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 }