Skip to content

Commit

Permalink
MCD: Add node-ip subcommand
Browse files Browse the repository at this point in the history
node-ip is a subcommand that allows the user to see which IP should the
node use in cases of multiple interface and multiple address nodes. This
is useful to prevent cases where Container Runtime related services bind
to an interface that is not reachable in the control plane.

It has two commands:

* show: Takes one or more Virtual IPs of the control plane and it gives
  you one eligible IP on stdout.

* set: Takes one or more Virtual IPs of the control plane and sets
  systemd service configuration for services like CRI-O or Kubelet that
  need to bind to the control plane.

Signed-off-by: Antoni Segura Puimedon <[email protected]>
  • Loading branch information
celebdor authored and vrutkovs committed Mar 13, 2020
1 parent 1c5ad92 commit 77cf01b
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 1 deletion.
87 changes: 87 additions & 0 deletions cmd/machine-config-daemon/node-ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"flag"
"fmt"
"net"

// Enable sha256 in container image references
_ "crypto/sha256"

"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/openshift/machine-config-operator/pkg/daemon/nodenet"
)

var nodeIPCmd = &cobra.Command{
Use: "node-ip",
DisableFlagsInUseLine: true,
Short: "Node IP tools",
Long: "Node IP has tools that aid in the configuration of nodes in platforms that use Virtual IPs",
}

var nodeIPShowCmd = &cobra.Command{
Use: "show",
DisableFlagsInUseLine: true,
Short: "Show a configured IP address that directly routes to the given Virtual IPs",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := show(cmd, args)
if err != nil {
glog.Exitf("error in node-ip show: %v\n", err)
}
},
}

var nodeIPSetCmd = &cobra.Command{
Use: "set",
DisableFlagsInUseLine: true,
Short: "Sets container runtime services to bind to a configured IP address that directly routes to the given virtual IPs",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := set(cmd, args)
if err != nil {
glog.Exitf("error in node-ip set: %v\n", err)
}
},
}

// init executes upon import
func init() {
rootCmd.AddCommand(nodeIPCmd)
nodeIPCmd.AddCommand(nodeIPShowCmd)
nodeIPCmd.AddCommand(nodeIPSetCmd)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
flag.Set("logtostderr", "true")
flag.Parse()
}

func show(_ *cobra.Command, args []string) error {
vips := make([]net.IP, len(args))
for i, arg := range args {
vips[i] = net.ParseIP(arg)
if vips[i] == nil {
return fmt.Errorf("Failed to parse IP address %s", arg)
}
glog.V(3).Infof("Parsed Virtual IP %s", vips[i])
}

nodeAddrs, err := nodenet.AddressesRouting(vips, nodenet.NonDeprecatedAddress, nodenet.NonDefaultRoute)
if err != nil {
return err
}

if len(nodeAddrs) > 0 {
fmt.Println(nodeAddrs[0])
} else {
return fmt.Errorf("Failed to find node IP")
}

return nil
}

func set(_ *cobra.Command, args []string) error {
return nil
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb
github.com/vishvananda/netlink v1.0.0
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 // indirect
golang.org/x/net v0.0.0-20191109021931-daa7c04131f5 // indirect
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed // indirect
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
golang.org/x/tools v0.0.0-20191002234911-9ade4c73f2af // indirect
gonum.org/v1/gonum v0.0.0-20190929233944-b20cf7805fc4 // indirect
Expand Down
141 changes: 141 additions & 0 deletions pkg/daemon/nodenet/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package nodenet

import (
"net"

"github.com/golang/glog"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)

// AddressFilter is a function type to filter addresses
type AddressFilter func(netlink.Addr) bool

// RouteFilter is a function type to filter routes
type RouteFilter func(netlink.Route) bool

func getAddrs() (addrMap map[netlink.Link][]netlink.Addr, err error) {
nlHandle, err := netlink.NewHandle()
defer nlHandle.Delete()
if err != nil {
return nil, err
}

links, err := nlHandle.LinkList()
if err != nil {
return nil, err
}

addrMap = make(map[netlink.Link][]netlink.Addr)
for _, link := range links {
addresses, err := nlHandle.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return nil, err
}
for _, address := range addresses {
if _, ok := addrMap[link]; ok {
addrMap[link] = append(addrMap[link], address)
} else {
addrMap[link] = []netlink.Addr{address}
}
}
}
glog.V(2).Infof("retrieved Address map %+v", addrMap)
return addrMap, nil
}

func getRouteMap() (routeMap map[int][]netlink.Route, err error) {
nlHandle, err := netlink.NewHandle()
defer nlHandle.Delete()
if err != nil {
return nil, err
}

routes, err := nlHandle.RouteList(nil, netlink.FAMILY_V6)
if err != nil {
return nil, err
}

routeMap = make(map[int][]netlink.Route)
for _, route := range routes {
if route.Protocol != unix.RTPROT_RA {
glog.V(4).Infof("Ignoring route non Router advertisement route %+v", route)
continue
}
if _, ok := routeMap[route.LinkIndex]; ok {
routeMap[route.LinkIndex] = append(routeMap[route.LinkIndex], route)
} else {
routeMap[route.LinkIndex] = []netlink.Route{route}
}
}

glog.V(2).Infof("Retrieved IPv6 route map %+v", routeMap)

return routeMap, nil
}

// NonDeprecatedAddress returns true if the address is IPv6 and has a preferred lifetime of 0
func NonDeprecatedAddress(address netlink.Addr) bool {
return !(net.IPv6len == len(address.IP) && address.PreferedLft == 0)
}

// NonDefaultRoute returns whether the passed Route is the default
func NonDefaultRoute(route netlink.Route) bool {
return route.Dst != nil
}

// AddressesRouting takes a slice of Virtual IPs and returns a slice of configured addresses in the current network namespace that directly route to those vips. You can optionally pass an AddressFilter and/or RouteFilter to further filter down which addresses are considered
func AddressesRouting(vips []net.IP, af AddressFilter, rf RouteFilter) ([]net.IP, error) {
addrMap, err := getAddrs()
if err != nil {
return nil, err
}

var routeMap map[int][]netlink.Route
matches := make([]net.IP, 0)
for link, addresses := range addrMap {
for _, address := range addresses {
maskPrefix, maskBits := address.Mask.Size()
if !af(address) {
continue
}
if net.IPv6len == len(address.IP) && maskPrefix == maskBits {
if routeMap == nil {
routeMap, err = getRouteMap()
if err != nil {
panic(err)
}
}
if routes, ok := routeMap[link.Attrs().Index]; ok {
for _, route := range routes {
if !rf(route) {
continue
}
routePrefix, _ := route.Dst.Mask.Size()
glog.V(4).Infof("Checking route %+v (mask %s) for address %+v", route, route.Dst.Mask, address)
if routePrefix != 0 {
containmentNet := net.IPNet{IP: address.IP, Mask: route.Dst.Mask}
for _, vip := range vips {
glog.V(3).Infof("Checking whether address %s with route %s contains VIP %s", address, route, vip)
if containmentNet.Contains(vip) {
glog.V(2).Infof("Address %s with route %s contains VIP %s", address, route, vip)
matches = append(matches, address.IP)
}
}
}
}
}
} else {
for _, vip := range vips {
glog.V(3).Infof("Checking whether address %s contains VIP %s", address, vip)
if address.Contains(vip) {
glog.V(2).Infof("Address %s contains VIP %s", address, vip)
matches = append(matches, address.IP)
}
}
}
}

}
return matches, nil
}

0 comments on commit 77cf01b

Please sign in to comment.