-
Notifications
You must be signed in to change notification settings - Fork 853
host-device: copy host interface IP addresses and routes into container #1257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,3 +28,4 @@ gopath/ | |
| .vagrant | ||
| .idea | ||
| /release-* | ||
| host-device | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,11 @@ type NetConf struct { | |
| RuntimeConfig struct { | ||
| DeviceID string `json:"deviceID,omitempty"` | ||
| } `json:"runtimeConfig,omitempty"` | ||
| // When true, capture the host interface's IP addresses and routes and apply | ||
| // them inside the container. Useful in cloud/virtual environments where L3 | ||
| // config is provisioned directly on the host device. Can be combined with | ||
| // IPAM to add extra addresses or routes on top of the host-provided ones. | ||
| UseInterfaceNetwork bool `json:"useInterfaceNetwork,omitempty"` | ||
|
|
||
| // for internal use | ||
| auxDevice string `json:"-"` // Auxiliary device name as appears on Auxiliary bus (/sys/bus/auxiliary) | ||
|
|
@@ -125,6 +130,12 @@ func cmdAdd(args *skel.CmdArgs) error { | |
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| interfaceNetworkEnabled := useInterfaceNetwork(cfg) | ||
| if interfaceNetworkEnabled && cfg.DPDKMode { | ||
| return fmt.Errorf("useInterfaceNetwork is not supported for dpdk-bound devices") | ||
| } | ||
|
|
||
| containerNs, err := ns.GetNS(args.Netns) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) | ||
|
|
@@ -138,12 +149,24 @@ func cmdAdd(args *skel.CmdArgs) error { | |
| }} | ||
|
|
||
| var contDev netlink.Link | ||
| var networkState *HostNetworkState | ||
| if !cfg.DPDKMode { | ||
| hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr, cfg.auxDevice) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to find host device: %v", err) | ||
| } | ||
|
|
||
| networkState = &HostNetworkState{ | ||
| HostIfName: hostDev.Attrs().Name, | ||
| HostLinkWasUp: hostDev.Attrs().Flags&net.FlagUp == net.FlagUp, | ||
| } | ||
| if interfaceNetworkEnabled { | ||
| err = captureHostNetworkState(networkState, hostDev) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| contDev, err = moveLinkIn(hostDev, containerNs, args.IfName) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to move link %v", err) | ||
|
|
@@ -153,6 +176,15 @@ func cmdAdd(args *skel.CmdArgs) error { | |
| result.Interfaces[0].Name = contDev.Attrs().Name | ||
| // Set the MAC address of the interface | ||
| result.Interfaces[0].Mac = contDev.Attrs().HardwareAddr.String() | ||
|
|
||
| if interfaceNetworkEnabled { | ||
| if err := networkState.applyToPod(containerNs, contDev); err != nil { | ||
| return err | ||
| } | ||
| if cfg.IPAM.Type == "" { | ||
| return printLinkWithNetworkState(contDev, cfg.CNIVersion, containerNs, networkState) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if cfg.IPAM.Type == "" { | ||
|
|
@@ -181,7 +213,7 @@ func cmdAdd(args *skel.CmdArgs) error { | |
| return err | ||
| } | ||
|
|
||
| if len(newResult.IPs) == 0 { | ||
| if !interfaceNetworkEnabled && len(newResult.IPs) == 0 { | ||
| return errors.New("IPAM plugin returned missing IP config") | ||
| } | ||
|
|
||
|
|
@@ -201,6 +233,10 @@ func cmdAdd(args *skel.CmdArgs) error { | |
| } | ||
| } | ||
|
|
||
| if interfaceNetworkEnabled { | ||
| mergeNetworkStateIntoResult(newResult, networkState) | ||
| } | ||
|
|
||
| newResult.DNS = cfg.DNS | ||
|
|
||
| return types.PrintResult(newResult, cfg.CNIVersion) | ||
|
|
@@ -496,6 +532,76 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error | |
| return types.PrintResult(&result, cniVersion) | ||
| } | ||
|
|
||
| func routeStateToCNIRoute(route routeState) *types.Route { | ||
| var dst net.IPNet | ||
| if route.Destination == "default" { | ||
| var gw net.IP | ||
| if route.Gateway != "" { | ||
| gw = net.ParseIP(route.Gateway) | ||
| } | ||
| dst = net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)} | ||
| if gw != nil && gw.To4() == nil { | ||
| dst = net.IPNet{IP: net.IPv6zero, Mask: net.CIDRMask(0, 128)} | ||
| } | ||
| } else { | ||
| _, parsedDst, err := net.ParseCIDR(route.Destination) | ||
| if err != nil { | ||
| return nil | ||
| } | ||
| dst = *parsedDst | ||
| } | ||
|
|
||
| cniRoute := &types.Route{Dst: dst} | ||
| if route.Gateway != "" { | ||
| cniRoute.GW = net.ParseIP(route.Gateway) | ||
| } | ||
| if route.Table != 0 { | ||
| cniRoute.Table = current.Int(route.Table) | ||
| } | ||
| if route.Scope != 0 { | ||
| cniRoute.Scope = current.Int(int(route.Scope)) | ||
| } | ||
| cniRoute.Priority = route.Metric | ||
| return cniRoute | ||
| } | ||
|
|
||
| func mergeNetworkStateIntoResult(result *current.Result, state *HostNetworkState) { | ||
| if state == nil { | ||
| return | ||
| } | ||
| for _, addr := range state.Addresses { | ||
| hostIP, ipNet, err := net.ParseCIDR(addr) | ||
| if err != nil { | ||
| continue | ||
| } | ||
| ipNet.IP = hostIP | ||
| result.IPs = append(result.IPs, ¤t.IPConfig{ | ||
| Interface: current.Int(0), | ||
| Address: *ipNet, | ||
| }) | ||
| } | ||
| for _, route := range state.Routes { | ||
| if cniRoute := routeStateToCNIRoute(route); cniRoute != nil { | ||
| result.Routes = append(result.Routes, cniRoute) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func printLinkWithNetworkState(dev netlink.Link, cniVersion string, containerNs ns.NetNS, state *HostNetworkState) error { | ||
| result := ¤t.Result{ | ||
| CNIVersion: current.ImplementedSpecVersion, | ||
| Interfaces: []*current.Interface{ | ||
| { | ||
| Name: dev.Attrs().Name, | ||
| Mac: dev.Attrs().HardwareAddr.String(), | ||
| Sandbox: containerNs.Path(), | ||
| }, | ||
| }, | ||
| } | ||
| mergeNetworkStateIntoResult(result, state) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imo if user decides to keep network config from the host then we should ignore IPAM or block the combination witt IPAM in the config. I think is either IPAM, or host network config (no ip is also valid config)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the point, but there are valid use cases for the combination: the host interface provides the base L3 config (addresses/routes from the cloud provider), and IPAM adds additional addresses on top (e.g. secondary IPs, service IPs). Blocking the combination would reduce flexibility for users who need both. The current merge behavior is additive - IPAM addresses are appended alongside host-captured ones. If you feel strongly, we could add a validation warning instead of an error, but I'd prefer to keep the flexibility. WDYT?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about conflicting default routes? IPs on the same prefix? |
||
| return types.PrintResult(result, cniVersion) | ||
| } | ||
|
|
||
| func linkFromPath(path string) (netlink.Link, error) { | ||
| entries, err := os.ReadDir(path) | ||
| if err != nil { | ||
|
|
@@ -670,9 +776,9 @@ func validateCniContainerInterface(intf current.Interface) error { | |
| } | ||
|
|
||
| func cmdStatus(args *skel.CmdArgs) error { | ||
| conf := NetConf{} | ||
| if err := json.Unmarshal(args.StdinData, &conf); err != nil { | ||
| return fmt.Errorf("failed to load netconf: %w", err) | ||
| conf, err := loadConf(args.StdinData) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if conf.IPAM.Type != "" { | ||
|
|
@@ -681,7 +787,11 @@ func cmdStatus(args *skel.CmdArgs) error { | |
| } | ||
| } | ||
|
|
||
| // TODO: Check if host device exists. | ||
| if !conf.DPDKMode { | ||
| if _, err := getLink(conf.Device, conf.HWAddr, conf.KernelPath, conf.PCIAddr, conf.auxDevice); err != nil { | ||
| return fmt.Errorf("failed to find host device: %v", err) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend to add comment to quickly mention what is 'UseInterfaceNetwork' because the option name is not intuitive (what 'useInterfaceNetwork' is used?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done - added an inline comment:
// When true, copy the host interface's IP addresses and routes into the container before IPAM runs.