Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ gopath/
.vagrant
.idea
/release-*
host-device
120 changes: 115 additions & 5 deletions plugins/main/host-device/host-device.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Copy link
Copy Markdown
Contributor

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?)

Copy link
Copy Markdown
Contributor Author

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.


// for internal use
auxDevice string `json:"-"` // Auxiliary device name as appears on Auxiliary bus (/sys/bus/auxiliary)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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 == "" {
Expand Down Expand Up @@ -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")
}

Expand All @@ -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)
Expand Down Expand Up @@ -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, &current.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 := &current.Result{
CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{
{
Name: dev.Attrs().Name,
Mac: dev.Attrs().HardwareAddr.String(),
Sandbox: containerNs.Path(),
},
},
}
mergeNetworkStateIntoResult(result, state)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 {
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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
}
Loading
Loading