github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/nettools/calico.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Some of the code is based on CNI's plugins/main/bridge/bridge.go, pkg/ip/link.go
    18  // Original copyright notice:
    19  //
    20  // Copyright 2014 CNI authors
    21  //
    22  // Licensed under the Apache License, Version 2.0 (the "License");
    23  // you may not use this file except in compliance with the License.
    24  // You may obtain a copy of the License at
    25  //
    26  //     http://www.apache.org/licenses/LICENSE-2.0
    27  //
    28  // Unless required by applicable law or agreed to in writing, software
    29  // distributed under the License is distributed on an "AS IS" BASIS,
    30  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    31  // See the License for the specific language governing permissions and
    32  // limitations under the License.
    33  
    34  package nettools
    35  
    36  import (
    37  	"errors"
    38  	"fmt"
    39  	"net"
    40  
    41  	"github.com/containernetworking/cni/pkg/ns"
    42  	cnitypes "github.com/containernetworking/cni/pkg/types"
    43  	cnicurrent "github.com/containernetworking/cni/pkg/types/current"
    44  	"github.com/golang/glog"
    45  	"github.com/vishvananda/netlink"
    46  )
    47  
    48  var calicoGatewayIP = net.IP{169, 254, 1, 1}
    49  
    50  // DetectCalico checks if the specified link in the current network
    51  // namespace is configured by Calico. It returns two boolean values
    52  // where the first one denotes whether Calico is used for the specified
    53  // link and the second one denotes whether Calico's default route
    54  // needs to be used. This approach is needed for multiple CNI use case
    55  // when the types of individual CNI plugins are not available.
    56  func DetectCalico(link netlink.Link) (bool, bool, error) {
    57  	haveCalico, haveCalicoGateway := false, false
    58  	routes, err := netlink.RouteList(link, FAMILY_V4)
    59  	if err != nil {
    60  		return false, false, fmt.Errorf("failed to list routes: %v", err)
    61  	}
    62  	for _, route := range routes {
    63  		switch {
    64  		case route.Protocol == RTPROT_KERNEL:
    65  			// route created by kernel
    66  		case route.LinkIndex == link.Attrs().Index && route.Gw == nil && route.Dst.IP.Equal(calicoGatewayIP):
    67  			haveCalico = true
    68  		case (route.Dst == nil || route.Dst.IP == nil) && route.Gw.Equal(calicoGatewayIP):
    69  			haveCalicoGateway = true
    70  		}
    71  	}
    72  	return haveCalico, haveCalico && haveCalicoGateway, nil
    73  }
    74  
    75  func getLinkForIPConfig(netConfig *cnicurrent.Result, ipConfigIndex int) (netlink.Link, error) {
    76  	if ipConfigIndex > len(netConfig.IPs) {
    77  		return nil, fmt.Errorf("ip config index out of range: %d", ipConfigIndex)
    78  	}
    79  
    80  	ipConfig := netConfig.IPs[ipConfigIndex]
    81  	if ipConfig.Interface >= len(netConfig.Interfaces) {
    82  		return nil, errors.New("interface index out of range in the CNI result")
    83  	}
    84  
    85  	if ipConfig.Version != "4" {
    86  		return nil, errors.New("skipping non-IPv4 config")
    87  	}
    88  
    89  	iface := netConfig.Interfaces[ipConfig.Interface]
    90  	if iface.Sandbox == "" {
    91  		return nil, errors.New("error: IP config has non-sandboxed interface")
    92  	}
    93  
    94  	link, err := netlink.LinkByName(iface.Name)
    95  	if err != nil {
    96  		return nil, fmt.Errorf("can't get link %q: %v", iface.Name, err)
    97  	}
    98  
    99  	return link, nil
   100  }
   101  
   102  func getDummyGateway(dummyNetwork *cnicurrent.Result) (net.IP, error) {
   103  	for n, ipConfig := range dummyNetwork.IPs {
   104  		var haveCalico bool
   105  		link, err := getLinkForIPConfig(dummyNetwork, n)
   106  		if err == nil {
   107  			haveCalico, _, err = DetectCalico(link)
   108  		}
   109  		if err != nil {
   110  			glog.Warningf("Calico fix: dummy network: skipping link for config %d: %v", n, err)
   111  			continue
   112  		}
   113  		if haveCalico {
   114  			return ipConfig.Address.IP, nil
   115  		}
   116  	}
   117  	return nil, errors.New("Calico fix: couldn't find dummy gateway")
   118  }
   119  
   120  // FixCalicoNetworking updates netConfig to make Calico work with
   121  // Virtlet's DHCP-server based scheme. It does so by throwing away
   122  // Calico's gateway and dev route and using a fake gateway instead.
   123  // The fake gateway provided by getDummyGateway() is just an IP
   124  // address allocated by Calico IPAM, it's needed for proper ARP
   125  // responses for VMs.
   126  // This function must be called from within the container network
   127  // namespace.
   128  func FixCalicoNetworking(netConfig *cnicurrent.Result, calicoSubnetSize int, getDummyNetwork func() (*cnicurrent.Result, string, error)) error {
   129  	for n, ipConfig := range netConfig.IPs {
   130  		link, err := getLinkForIPConfig(netConfig, n)
   131  		if err != nil {
   132  			glog.Warningf("Calico fix: skipping link for config %d: %v", n, err)
   133  			continue
   134  		}
   135  		haveCalico, haveCalicoGateway, err := DetectCalico(link)
   136  		if err != nil {
   137  			return err
   138  		}
   139  		if !haveCalico {
   140  			continue
   141  		}
   142  		ipConfig.Address.Mask = net.CIDRMask(calicoSubnetSize, 32)
   143  		if haveCalicoGateway {
   144  			dummyNetwork, nsPath, err := getDummyNetwork()
   145  			if err != nil {
   146  				return err
   147  			}
   148  			dummyNS, err := ns.GetNS(nsPath)
   149  			if err != nil {
   150  				return err
   151  			}
   152  			if err := dummyNS.Do(func(ns.NetNS) error {
   153  				allLinks, err := netlink.LinkList()
   154  				if err != nil {
   155  					return fmt.Errorf("failed to list links inside the dummy netns: %v", err)
   156  				}
   157  				dummyNetwork, err := ValidateAndFixCNIResult(dummyNetwork, nsPath, allLinks)
   158  				if err != nil {
   159  					return err
   160  				}
   161  				dummyGateway, err := getDummyGateway(dummyNetwork)
   162  				if err != nil {
   163  					return err
   164  				}
   165  				ipConfig.Gateway = dummyGateway
   166  				return nil
   167  			}); err != nil {
   168  				return err
   169  			}
   170  
   171  			var newRoutes []*cnitypes.Route
   172  			// remove the default gateway
   173  			for _, r := range netConfig.Routes {
   174  				if r.Dst.Mask != nil {
   175  					ones, _ := r.Dst.Mask.Size()
   176  					if ones == 0 {
   177  						continue
   178  					}
   179  				}
   180  				newRoutes = append(newRoutes, r)
   181  			}
   182  			netConfig.Routes = append(newRoutes, &cnitypes.Route{
   183  				Dst: net.IPNet{
   184  					IP:   net.IP{0, 0, 0, 0},
   185  					Mask: net.IPMask{0, 0, 0, 0},
   186  				},
   187  				GW: ipConfig.Gateway,
   188  			})
   189  		}
   190  	}
   191  	return nil
   192  }