| // Copyright 2017 CNI authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package hns |
| |
| import ( |
| "fmt" |
| "net" |
| "strings" |
| |
| "github.com/Microsoft/hcsshim" |
| "github.com/Microsoft/hcsshim/hcn" |
| |
| "github.com/containernetworking/cni/pkg/types" |
| current "github.com/containernetworking/cni/pkg/types/100" |
| |
| "github.com/containernetworking/plugins/pkg/errors" |
| ) |
| |
| const ( |
| pauseContainerNetNS = "none" |
| ) |
| |
| type EndpointInfo struct { |
| EndpointName string |
| DNS types.DNS |
| NetworkName string |
| NetworkId string |
| Gateway net.IP |
| IpAddress net.IP |
| MacAddress string |
| } |
| |
| // GetSandboxContainerID returns the sandbox ID of this pod. |
| func GetSandboxContainerID(containerID string, netNs string) string { |
| if len(netNs) != 0 && netNs != pauseContainerNetNS { |
| splits := strings.SplitN(netNs, ":", 2) |
| if len(splits) == 2 { |
| containerID = splits[1] |
| } |
| } |
| |
| return containerID |
| } |
| |
| // GetIpString returns the given IP as a string. |
| func GetIpString(ip *net.IP) string { |
| if len(*ip) == 0 { |
| return "" |
| } else { |
| return ip.String() |
| } |
| } |
| |
| // GetDefaultDestinationPrefix returns the default destination prefix according to the given IP type. |
| func GetDefaultDestinationPrefix(ip *net.IP) string { |
| destinationPrefix := "0.0.0.0/0" |
| if ip.To4() == nil { |
| destinationPrefix = "::/0" |
| } |
| return destinationPrefix |
| } |
| |
| // ConstructEndpointName constructs endpoint id which is used to identify an endpoint from HNS/HCN. |
| func ConstructEndpointName(containerID string, netNs string, networkName string) string { |
| return GetSandboxContainerID(containerID, netNs) + "_" + networkName |
| } |
| |
| // GenerateHnsEndpoint generates an HNSEndpoint with given info and config. |
| func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) { |
| // run the IPAM plugin and get back the config to apply |
| hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName) |
| if err != nil && !hcsshim.IsNotExist(err) { |
| return nil, errors.Annotatef(err, "failed to get HNSEndpoint %s", epInfo.EndpointName) |
| } |
| |
| if hnsEndpoint != nil { |
| if strings.EqualFold(hnsEndpoint.VirtualNetwork, epInfo.NetworkId) { |
| return nil, fmt.Errorf("HNSEndpoint %s is already existed", epInfo.EndpointName) |
| } |
| // remove endpoint if corrupted |
| if _, err = hnsEndpoint.Delete(); err != nil { |
| return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epInfo.EndpointName) |
| } |
| } |
| |
| if n.LoopbackDSR { |
| n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress) |
| } |
| hnsEndpoint = &hcsshim.HNSEndpoint{ |
| Name: epInfo.EndpointName, |
| VirtualNetwork: epInfo.NetworkId, |
| DNSServerList: strings.Join(epInfo.DNS.Nameservers, ","), |
| DNSSuffix: strings.Join(epInfo.DNS.Search, ","), |
| GatewayAddress: GetIpString(&epInfo.Gateway), |
| IPAddress: epInfo.IpAddress, |
| Policies: n.GetHNSEndpointPolicies(), |
| } |
| return hnsEndpoint, nil |
| } |
| |
| // RemoveHnsEndpoint detaches the given name endpoint from container specified by containerID, |
| // or removes the given name endpoint completely. |
| func RemoveHnsEndpoint(epName string, netns string, containerID string) error { |
| if len(netns) == 0 { |
| return nil |
| } |
| |
| hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName) |
| if err != nil { |
| if hcsshim.IsNotExist(err) { |
| return nil |
| } |
| return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName) |
| } |
| |
| // for shared endpoint, detach it from the container |
| if netns != pauseContainerNetNS { |
| _ = hnsEndpoint.ContainerDetach(containerID) |
| return nil |
| } |
| |
| // for removing the endpoint completely, hot detach is used at first |
| _ = hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id) |
| _, _ = hnsEndpoint.Delete() |
| return nil |
| } |
| |
| type HnsEndpointMakerFunc func() (*hcsshim.HNSEndpoint, error) |
| |
| // AddHnsEndpoint attaches an HNSEndpoint to a container specified by containerID. |
| func AddHnsEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint HnsEndpointMakerFunc) (*hcsshim.HNSEndpoint, error) { |
| hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName) |
| if err != nil { |
| if !hcsshim.IsNotExist(err) { |
| return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName) |
| } |
| } |
| |
| // for shared endpoint, we expect that the endpoint already exists |
| if netns != pauseContainerNetNS { |
| if hnsEndpoint == nil { |
| return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName) |
| } |
| } |
| |
| // verify the existing endpoint is corrupted or not |
| if hnsEndpoint != nil { |
| if !strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) { |
| if _, err := hnsEndpoint.Delete(); err != nil { |
| return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epName) |
| } |
| hnsEndpoint = nil |
| } |
| } |
| |
| // create endpoint if not found |
| var isNewEndpoint bool |
| if hnsEndpoint == nil { |
| if hnsEndpoint, err = makeEndpoint(); err != nil { |
| return nil, errors.Annotate(err, "failed to make a new HNSEndpoint") |
| } |
| if hnsEndpoint, err = hnsEndpoint.Create(); err != nil { |
| return nil, errors.Annotate(err, "failed to create the new HNSEndpoint") |
| } |
| isNewEndpoint = true |
| } |
| |
| // attach to container |
| if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil { |
| if isNewEndpoint { |
| if err := RemoveHnsEndpoint(epName, netns, containerID); err != nil { |
| return nil, errors.Annotatef(err, "failed to remove the new HNSEndpoint %s after attaching container %s failure", hnsEndpoint.Id, containerID) |
| } |
| } else if hcsshim.ErrComputeSystemDoesNotExist == err { |
| return hnsEndpoint, nil |
| } |
| return nil, errors.Annotatef(err, "failed to attach container %s to HNSEndpoint %s", containerID, hnsEndpoint.Id) |
| } |
| return hnsEndpoint, nil |
| } |
| |
| // ConstructHnsResult constructs the CNI result for the HNSEndpoint. |
| func ConstructHnsResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) { |
| resultInterface := ¤t.Interface{ |
| Name: hnsEndpoint.Name, |
| Mac: hnsEndpoint.MacAddress, |
| } |
| _, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix) |
| if err != nil { |
| return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix) |
| } |
| |
| resultIPConfig := ¤t.IPConfig{ |
| Address: net.IPNet{ |
| IP: hnsEndpoint.IPAddress, |
| Mask: ipSubnet.Mask}, |
| Gateway: net.ParseIP(hnsEndpoint.GatewayAddress), |
| } |
| result := ¤t.Result{ |
| CNIVersion: current.ImplementedSpecVersion, |
| Interfaces: []*current.Interface{resultInterface}, |
| IPs: []*current.IPConfig{resultIPConfig}, |
| DNS: types.DNS{ |
| Search: strings.Split(hnsEndpoint.DNSSuffix, ","), |
| Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","), |
| }, |
| } |
| |
| return result, nil |
| } |
| |
| // GenerateHcnEndpoint generates a HostComputeEndpoint with given info and config. |
| func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) { |
| // run the IPAM plugin and get back the config to apply |
| hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName) |
| if err != nil && !hcn.IsNotFoundError(err) { |
| return nil, errors.Annotatef(err, "failed to get HostComputeEndpoint %s", epInfo.EndpointName) |
| } |
| |
| // verify the existing endpoint is corrupted or not |
| if hcnEndpoint != nil { |
| if strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) { |
| return nil, fmt.Errorf("HostComputeNetwork %s is already existed", epInfo.EndpointName) |
| } |
| // remove endpoint if corrupted |
| if err := hcnEndpoint.Delete(); err != nil { |
| return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epInfo.EndpointName) |
| } |
| } |
| |
| if n.LoopbackDSR { |
| n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress) |
| } |
| hcnEndpoint = &hcn.HostComputeEndpoint{ |
| SchemaVersion: hcn.SchemaVersion{ |
| Major: 2, |
| Minor: 0, |
| }, |
| Name: epInfo.EndpointName, |
| MacAddress: epInfo.MacAddress, |
| HostComputeNetwork: epInfo.NetworkId, |
| Dns: hcn.Dns{ |
| Domain: epInfo.DNS.Domain, |
| Search: epInfo.DNS.Search, |
| ServerList: epInfo.DNS.Nameservers, |
| Options: epInfo.DNS.Options, |
| }, |
| Routes: []hcn.Route{ |
| { |
| NextHop: GetIpString(&epInfo.Gateway), |
| DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway), |
| }, |
| }, |
| IpConfigurations: []hcn.IpConfig{ |
| { |
| IpAddress: GetIpString(&epInfo.IpAddress), |
| }, |
| }, |
| Policies: n.GetHostComputeEndpointPolicies(), |
| } |
| return hcnEndpoint, nil |
| } |
| |
| // RemoveHcnEndpoint removes the given name endpoint from namespace. |
| func RemoveHcnEndpoint(epName string) error { |
| hcnEndpoint, err := hcn.GetEndpointByName(epName) |
| if err != nil { |
| if hcn.IsNotFoundError(err) { |
| return nil |
| } |
| return errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName) |
| } |
| epNamespace, err := hcn.GetNamespaceByID(hcnEndpoint.HostComputeNamespace) |
| if err != nil && !hcn.IsNotFoundError(err) { |
| return errors.Annotatef(err, "failed to get HostComputeNamespace %s", epName) |
| } |
| if epNamespace != nil { |
| err = hcn.RemoveNamespaceEndpoint(hcnEndpoint.HostComputeNamespace, hcnEndpoint.Id) |
| if err != nil && !hcn.IsNotFoundError(err) { |
| return errors.Annotatef(err,"error removing endpoint: %s from namespace", epName) |
| } |
| } |
| |
| err = hcnEndpoint.Delete() |
| if err != nil { |
| return errors.Annotatef(err, "failed to remove HostComputeEndpoint %s", epName) |
| } |
| return nil |
| } |
| |
| type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error) |
| |
| // AddHcnEndpoint attaches a HostComputeEndpoint to the given namespace. |
| func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) { |
| hcnEndpoint, err := hcn.GetEndpointByName(epName) |
| if err != nil { |
| if !hcn.IsNotFoundError(err) { |
| return nil, errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName) |
| } |
| } |
| |
| // verify the existing endpoint is corrupted or not |
| if hcnEndpoint != nil { |
| if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, expectedNetworkId) { |
| if err := hcnEndpoint.Delete(); err != nil { |
| return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epName) |
| } |
| hcnEndpoint = nil |
| } |
| } |
| |
| // create endpoint if not found |
| var isNewEndpoint bool |
| if hcnEndpoint == nil { |
| if hcnEndpoint, err = makeEndpoint(); err != nil { |
| return nil, errors.Annotate(err, "failed to make a new HostComputeEndpoint") |
| } |
| if hcnEndpoint, err = hcnEndpoint.Create(); err != nil { |
| return nil, errors.Annotate(err, "failed to create the new HostComputeEndpoint") |
| } |
| isNewEndpoint = true |
| } |
| |
| // add to namespace |
| err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id) |
| if err != nil { |
| if isNewEndpoint { |
| if err := RemoveHcnEndpoint(epName); err != nil { |
| return nil, errors.Annotatef(err, "failed to remove the new HostComputeEndpoint %s after adding HostComputeNamespace %s failure", epName, namespace) |
| } |
| } |
| return nil, errors.Annotatef(err, "failed to add HostComputeEndpoint %s to HostComputeNamespace %s", epName, namespace) |
| } |
| return hcnEndpoint, nil |
| } |
| |
| // ConstructHcnResult constructs the CNI result for the HostComputeEndpoint. |
| func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) { |
| resultInterface := ¤t.Interface{ |
| Name: hcnEndpoint.Name, |
| Mac: hcnEndpoint.MacAddress, |
| } |
| _, ipSubnet, err := net.ParseCIDR(hcnNetwork.Ipams[0].Subnets[0].IpAddressPrefix) |
| if err != nil { |
| return nil, err |
| } |
| |
| ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress) |
| resultIPConfig := ¤t.IPConfig{ |
| Address: net.IPNet{ |
| IP: ipAddress, |
| Mask: ipSubnet.Mask}, |
| Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop), |
| } |
| result := ¤t.Result{ |
| CNIVersion: current.ImplementedSpecVersion, |
| Interfaces: []*current.Interface{resultInterface}, |
| IPs: []*current.IPConfig{resultIPConfig}, |
| DNS: types.DNS{ |
| Search: hcnEndpoint.Dns.Search, |
| Nameservers: hcnEndpoint.Dns.ServerList, |
| Options: hcnEndpoint.Dns.Options, |
| Domain: hcnEndpoint.Dns.Domain, |
| }, |
| } |
| |
| return result, nil |
| } |