371 lines
11 KiB
Go
371 lines
11 KiB
Go
// 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"
|
|
"github.com/containernetworking/cni/pkg/types/current"
|
|
"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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// short function so we know when to return "" for a string
|
|
func GetIpString(ip *net.IP) string {
|
|
if len(*ip) == 0 {
|
|
return ""
|
|
} else {
|
|
return ip.String()
|
|
}
|
|
}
|
|
|
|
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 endpoint %q", epInfo.EndpointName)
|
|
}
|
|
|
|
if hnsEndpoint != nil {
|
|
if hnsEndpoint.VirtualNetwork != epInfo.NetworkId {
|
|
_, err = hnsEndpoint.Delete()
|
|
if err != nil {
|
|
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
|
}
|
|
hnsEndpoint = nil
|
|
}
|
|
}
|
|
|
|
if n.LoopbackDSR {
|
|
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
|
}
|
|
if hnsEndpoint == nil {
|
|
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.MarshalPolicies(),
|
|
}
|
|
}
|
|
return hnsEndpoint, nil
|
|
}
|
|
|
|
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 endpoint %q", epInfo.EndpointName)
|
|
}
|
|
|
|
if hcnEndpoint != nil {
|
|
// If the endpont already exists, then we should return error unless
|
|
// the endpoint is based on a different network then delete
|
|
// should that fail return error
|
|
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
|
|
err = hcnEndpoint.Delete()
|
|
if err != nil {
|
|
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
|
|
}
|
|
}
|
|
|
|
if hcnEndpoint == nil {
|
|
routes := []hcn.Route{
|
|
{
|
|
NextHop: GetIpString(&epInfo.Gateway),
|
|
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
|
|
},
|
|
}
|
|
|
|
hcnDns := hcn.Dns{
|
|
Search: epInfo.DNS.Search,
|
|
ServerList: epInfo.DNS.Nameservers,
|
|
}
|
|
|
|
hcnIpConfig := hcn.IpConfig{
|
|
IpAddress: GetIpString(&epInfo.IpAddress),
|
|
}
|
|
ipConfigs := []hcn.IpConfig{hcnIpConfig}
|
|
|
|
if n.LoopbackDSR {
|
|
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
|
}
|
|
hcnEndpoint = &hcn.HostComputeEndpoint{
|
|
SchemaVersion: hcn.Version{Major: 2},
|
|
Name: epInfo.EndpointName,
|
|
HostComputeNetwork: epInfo.NetworkId,
|
|
Dns: hcnDns,
|
|
Routes: routes,
|
|
IpConfigurations: ipConfigs,
|
|
Policies: func() []hcn.EndpointPolicy {
|
|
if n.HcnPolicyArgs == nil {
|
|
n.HcnPolicyArgs = []hcn.EndpointPolicy{}
|
|
}
|
|
return n.HcnPolicyArgs
|
|
}(),
|
|
}
|
|
}
|
|
return hcnEndpoint, nil
|
|
}
|
|
|
|
// ConstructEndpointName constructs enpointId which is used to identify an endpoint from HNS
|
|
// There is a special consideration for netNs name here, which is required for Windows Server 1709
|
|
// containerID is the Id of the container on which the endpoint is worked on
|
|
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
|
|
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
|
|
}
|
|
|
|
// DeprovisionEndpoint removes an endpoint from the container by sending a Detach request to HNS
|
|
// For shared endpoint, ContainerDetach is used
|
|
// for removing the endpoint completely, HotDetachEndpoint is used
|
|
func DeprovisionEndpoint(epName string, netns string, containerID string) error {
|
|
if len(netns) == 0 {
|
|
return nil
|
|
}
|
|
|
|
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
|
|
|
if hcsshim.IsNotExist(err) {
|
|
return nil
|
|
} else if err != nil {
|
|
return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
|
}
|
|
|
|
if netns != pauseContainerNetNS {
|
|
// Shared endpoint removal. Do not remove the endpoint.
|
|
hnsEndpoint.ContainerDetach(containerID)
|
|
return nil
|
|
}
|
|
|
|
// Do not consider this as failure, else this would leak endpoints
|
|
hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
|
|
|
|
// Do not return error
|
|
hnsEndpoint.Delete()
|
|
|
|
return nil
|
|
}
|
|
|
|
type EndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)
|
|
|
|
// ProvisionEndpoint provisions an endpoint to a container specified by containerID.
|
|
// If an endpoint already exists, the endpoint is reused.
|
|
// This call is idempotent
|
|
func ProvisionEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint EndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
|
|
// On the second add call we expect that the endpoint already exists. If it
|
|
// does not then we should return an error.
|
|
if netns != pauseContainerNetNS {
|
|
_, err := hcsshim.GetHNSEndpointByName(epName)
|
|
if err != nil {
|
|
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
|
}
|
|
}
|
|
|
|
// check if endpoint already exists
|
|
createEndpoint := true
|
|
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
|
if hnsEndpoint != nil && strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
|
|
createEndpoint = false
|
|
}
|
|
|
|
if createEndpoint {
|
|
if hnsEndpoint != nil {
|
|
if _, err = hnsEndpoint.Delete(); err != nil {
|
|
return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint")
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
}
|
|
|
|
// hot attach
|
|
if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
|
|
if createEndpoint {
|
|
err := DeprovisionEndpoint(epName, netns, containerID)
|
|
if err != nil {
|
|
return nil, errors.Annotatef(err, "failed to Deprovsion after HotAttach failure")
|
|
}
|
|
}
|
|
if hcsshim.ErrComputeSystemDoesNotExist == err {
|
|
return hnsEndpoint, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return hnsEndpoint, nil
|
|
}
|
|
|
|
type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)
|
|
|
|
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string,
|
|
makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
|
|
|
|
hcnEndpoint, err := makeEndpoint()
|
|
if err != nil {
|
|
return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
|
|
}
|
|
|
|
if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
|
|
return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
|
|
}
|
|
|
|
err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
|
|
if err != nil {
|
|
err := RemoveHcnEndpoint(epName)
|
|
if err != nil {
|
|
return nil, errors.Annotatef(err, "failed to Remove Endpoint after AddNamespaceEndpoint failure")
|
|
}
|
|
return nil, errors.Annotate(err, "failed to Add endpoint to namespace")
|
|
}
|
|
return hcnEndpoint, nil
|
|
|
|
}
|
|
|
|
// ConstructResult constructs the CNI result for the endpoint
|
|
func ConstructResult(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)
|
|
}
|
|
|
|
var ipVersion string
|
|
if ipv4 := hnsEndpoint.IPAddress.To4(); ipv4 != nil {
|
|
ipVersion = "4"
|
|
} else if ipv6 := hnsEndpoint.IPAddress.To16(); ipv6 != nil {
|
|
ipVersion = "6"
|
|
} else {
|
|
return nil, fmt.Errorf("IPAddress of HNSEndpoint %s isn't a valid ipv4 or ipv6 Address", hnsEndpoint.Name)
|
|
}
|
|
|
|
resultIPConfig := ¤t.IPConfig{
|
|
Version: ipVersion,
|
|
Address: net.IPNet{
|
|
IP: hnsEndpoint.IPAddress,
|
|
Mask: ipSubnet.Mask},
|
|
Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
|
|
}
|
|
result := ¤t.Result{}
|
|
result.Interfaces = []*current.Interface{resultInterface}
|
|
result.IPs = []*current.IPConfig{resultIPConfig}
|
|
result.DNS = types.DNS{
|
|
Search: strings.Split(hnsEndpoint.DNSSuffix, ","),
|
|
Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// This version follows the v2 workflow of removing the endpoint from the namespace and deleting it
|
|
func RemoveHcnEndpoint(epName string) error {
|
|
hcnEndpoint, err := hcn.GetEndpointByName(epName)
|
|
if hcn.IsNotFoundError(err) {
|
|
return nil
|
|
} else if err != nil {
|
|
_ = fmt.Errorf("[win-cni] Failed to find endpoint %v, err:%v", epName, err)
|
|
return err
|
|
}
|
|
if hcnEndpoint != nil {
|
|
err = hcnEndpoint.Delete()
|
|
if err != nil {
|
|
return fmt.Errorf("[win-cni] Failed to delete endpoint %v, err:%v", epName, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
var ipVersion string
|
|
ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress)
|
|
if ipv4 := ipAddress.To4(); ipv4 != nil {
|
|
ipVersion = "4"
|
|
} else if ipv6 := ipAddress.To16(); ipv6 != nil {
|
|
ipVersion = "6"
|
|
} else {
|
|
return nil, fmt.Errorf("[win-cni] The IPAddress of hnsEndpoint isn't a valid ipv4 or ipv6 Address.")
|
|
}
|
|
|
|
resultIPConfig := ¤t.IPConfig{
|
|
Version: ipVersion,
|
|
Address: net.IPNet{
|
|
IP: ipAddress,
|
|
Mask: ipSubnet.Mask},
|
|
Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop),
|
|
}
|
|
result := ¤t.Result{}
|
|
result.Interfaces = []*current.Interface{resultInterface}
|
|
result.IPs = []*current.IPConfig{resultIPConfig}
|
|
result.DNS = types.DNS{
|
|
Search: hcnEndpoint.Dns.Search,
|
|
Nameservers: hcnEndpoint.Dns.ServerList,
|
|
}
|
|
|
|
return result, nil
|
|
}
|