diff --git a/cmd/machine-config-daemon/node-ip.go b/cmd/machine-config-daemon/node-ip.go new file mode 100644 index 0000000000..898abeeb4f --- /dev/null +++ b/cmd/machine-config-daemon/node-ip.go @@ -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 +} diff --git a/go.mod b/go.mod index 1f84f9526a..50543a4ca3 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/pkg/daemon/nodenet/utils.go b/pkg/daemon/nodenet/utils.go new file mode 100644 index 0000000000..810957f45b --- /dev/null +++ b/pkg/daemon/nodenet/utils.go @@ -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 +} diff --git a/templates/worker/00-worker/baremetal/files/NetworkManager-non-virtual-ip-prepender.yaml b/templates/worker/00-worker/baremetal/files/NetworkManager-non-virtual-ip-prepender.yaml index 98481685f7..a9f7dda77c 100644 --- a/templates/worker/00-worker/baremetal/files/NetworkManager-non-virtual-ip-prepender.yaml +++ b/templates/worker/00-worker/baremetal/files/NetworkManager-non-virtual-ip-prepender.yaml @@ -9,7 +9,7 @@ contents: case "$STATUS" in pre-up) logger -s "NM non-virtual-ip-prepender triggered by pre-upping ${1}." - NON_VIRTUAL_IP=$(/usr/local/bin/non_virtual_ip \ + NON_VIRTUAL_IP=$(/usr/local/bin/machine-config-daemon node-ip show \ "{{.Infra.Status.PlatformStatus.BareMetal.APIServerInternalIP}}" \ "{{.Infra.Status.PlatformStatus.BareMetal.NodeDNSIP}}" \ "{{.Infra.Status.PlatformStatus.BareMetal.IngressIP}}") @@ -38,4 +38,4 @@ contents: ;; *) ;; - esac \ No newline at end of file + esac diff --git a/templates/worker/00-worker/baremetal/files/baremetal-non-virtual-ip.yaml b/templates/worker/00-worker/baremetal/files/baremetal-non-virtual-ip.yaml deleted file mode 100644 index d82386bfcb..0000000000 --- a/templates/worker/00-worker/baremetal/files/baremetal-non-virtual-ip.yaml +++ /dev/null @@ -1,12 +0,0 @@ -filesystem: "root" -mode: 0755 -path: "/usr/local/bin/non_virtual_ip" -contents: - inline: | - #!/usr/bin/bash - podman run --rm \ - --authfile /var/lib/kubelet/config.json \ - --net=host \ - --entrypoint=/usr/bin/non-virtual-ip \ - {{ .Images.baremetalRuntimeCfgImage }} \ - ${@} diff --git a/templates/worker/00-worker/openstack/files/NetworkManager-non-virtual-ip-prepender.yaml b/templates/worker/00-worker/openstack/files/NetworkManager-non-virtual-ip-prepender.yaml index 9607da4138..ab49fb91fa 100644 --- a/templates/worker/00-worker/openstack/files/NetworkManager-non-virtual-ip-prepender.yaml +++ b/templates/worker/00-worker/openstack/files/NetworkManager-non-virtual-ip-prepender.yaml @@ -9,7 +9,7 @@ contents: case "$STATUS" in pre-up) logger -s "NM non-virtual-ip-prepender triggered by pre-upping ${1}." - NON_VIRTUAL_IP=$(/usr/local/bin/non_virtual_ip \ + NON_VIRTUAL_IP=$(/usr/local/bin/machine-config-daemon node-ip show \ "{{.Infra.Status.PlatformStatus.OpenStack.APIServerInternalIP}}" \ "{{.Infra.Status.PlatformStatus.OpenStack.NodeDNSIP}}" \ "{{.Infra.Status.PlatformStatus.OpenStack.IngressIP}}") diff --git a/templates/worker/00-worker/openstack/files/openstack-non-virtual-ip.yaml b/templates/worker/00-worker/openstack/files/openstack-non-virtual-ip.yaml deleted file mode 100644 index d82386bfcb..0000000000 --- a/templates/worker/00-worker/openstack/files/openstack-non-virtual-ip.yaml +++ /dev/null @@ -1,12 +0,0 @@ -filesystem: "root" -mode: 0755 -path: "/usr/local/bin/non_virtual_ip" -contents: - inline: | - #!/usr/bin/bash - podman run --rm \ - --authfile /var/lib/kubelet/config.json \ - --net=host \ - --entrypoint=/usr/bin/non-virtual-ip \ - {{ .Images.baremetalRuntimeCfgImage }} \ - ${@} diff --git a/templates/worker/00-worker/ovirt/files/ovirt-non-virtual-ip.yaml b/templates/worker/00-worker/ovirt/files/ovirt-non-virtual-ip.yaml deleted file mode 100644 index d82386bfcb..0000000000 --- a/templates/worker/00-worker/ovirt/files/ovirt-non-virtual-ip.yaml +++ /dev/null @@ -1,12 +0,0 @@ -filesystem: "root" -mode: 0755 -path: "/usr/local/bin/non_virtual_ip" -contents: - inline: | - #!/usr/bin/bash - podman run --rm \ - --authfile /var/lib/kubelet/config.json \ - --net=host \ - --entrypoint=/usr/bin/non-virtual-ip \ - {{ .Images.baremetalRuntimeCfgImage }} \ - ${@}