// 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 ( "bytes" "encoding/json" "errors" "fmt" "net" "strconv" "strings" "github.com/Microsoft/hcsshim/hcn" "github.com/buger/jsonparser" "github.com/containernetworking/cni/pkg/types" ) // NetConf is the CNI spec type NetConf struct { types.NetConf // ApiVersion specifies the policies type of HNS or HCN, select one of [1, 2]. // HNS is the v1 API, which is the default version and applies to dockershim. // HCN is the v2 API, which can leverage HostComputeNamespace and use in containerd. ApiVersion int `json:"apiVersion,omitempty"` // Policies specifies the policy list for HNSEndpoint or HostComputeEndpoint. Policies []Policy `json:"policies,omitempty"` // RuntimeConfig represents the options to be passed in by the runtime. RuntimeConfig RuntimeConfig `json:"runtimeConfig"` // LoopbackDSR specifies whether to support loopback direct server return. LoopbackDSR bool `json:"loopbackDSR,omitempty"` } type RuntimeDNS struct { Nameservers []string `json:"servers,omitempty"` Search []string `json:"searches,omitempty"` } type PortMapEntry struct { HostPort int `json:"hostPort"` ContainerPort int `json:"containerPort"` Protocol string `json:"protocol"` HostIP string `json:"hostIP,omitempty"` } // constants of the supported Windows Socket protocol, // ref to https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.protocoltype. var protocolEnums = map[string]uint32{ "icmpv4": 1, "igmp": 2, "tcp": 6, "udp": 17, "icmpv6": 58, } func (p *PortMapEntry) GetProtocolEnum() (uint32, error) { var u, err = strconv.ParseUint(p.Protocol, 0, 10) if err != nil { var pe, exist = protocolEnums[strings.ToLower(p.Protocol)] if !exist { return 0, errors.New("invalid protocol supplied to port mapping policy") } return pe, nil } return uint32(u), nil } type RuntimeConfig struct { DNS RuntimeDNS `json:"dns"` PortMaps []PortMapEntry `json:"portMappings,omitempty"` } type Policy struct { Name string `json:"name"` Value json.RawMessage `json:"value"` } // GetHNSEndpointPolicies converts the configuration policies to HNSEndpoint policies. func (n *NetConf) GetHNSEndpointPolicies() []json.RawMessage { result := make([]json.RawMessage, 0, len(n.Policies)) for _, p := range n.Policies { if !strings.EqualFold(p.Name, "EndpointPolicy") { continue } result = append(result, p.Value) } return result } // GetHostComputeEndpointPolicies converts the configuration policies to HostComputeEndpoint policies. func (n *NetConf) GetHostComputeEndpointPolicies() []hcn.EndpointPolicy { result := make([]hcn.EndpointPolicy, 0, len(n.Policies)) for _, p := range n.Policies { if !strings.EqualFold(p.Name, "EndpointPolicy") { continue } var policy hcn.EndpointPolicy if err := json.Unmarshal(p.Value, &policy); err != nil { continue } result = append(result, policy) } return result } // GetDNS returns the DNS values if they are there use that else use netconf supplied DNS. func (n *NetConf) GetDNS() types.DNS { dnsResult := n.DNS if len(n.RuntimeConfig.DNS.Nameservers) > 0 { dnsResult.Nameservers = n.RuntimeConfig.DNS.Nameservers } if len(n.RuntimeConfig.DNS.Search) > 0 { dnsResult.Search = n.RuntimeConfig.DNS.Search } return dnsResult } // ApplyLoopbackDSRPolicy configures the given IP to support loopback DSR. func (n *NetConf) ApplyLoopbackDSRPolicy(ip *net.IP) { if err := hcn.DSRSupported(); err != nil || ip == nil { return } toPolicyValue := func(addr string) json.RawMessage { if n.ApiVersion == 2 { return bprintf(`{"Type": "OutBoundNAT", "Settings": {"Destinations": ["%s"]}}`, addr) } return bprintf(`{"Type": "OutBoundNAT", "Destinations": ["%s"]}`, addr) } ipBytes := []byte(ip.String()) // find OutBoundNAT policy for i := range n.Policies { p := &n.Policies[i] if !strings.EqualFold(p.Name, "EndpointPolicy") { continue } // filter OutBoundNAT policy typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type") if typeValue != "OutBoundNAT" { continue } // parse destination address list var ( destinationsValue []byte dt jsonparser.ValueType ) if n.ApiVersion == 2 { destinationsValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "Destinations") } else { destinationsValue, dt, _, _ = jsonparser.Get(p.Value, "Destinations") } // skip if Destinations/DestinationList field is not found if dt == jsonparser.NotExist { continue } // return if found the given address if dt == jsonparser.Array { var found bool _, _ = jsonparser.ArrayEach(destinationsValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { if dataType == jsonparser.String && len(value) != 0 { if bytes.Compare(value, ipBytes) == 0 { found = true } } }) if found { return } } } // or add a new OutBoundNAT if not found n.Policies = append(n.Policies, Policy{ Name: "EndpointPolicy", Value: toPolicyValue(ip.String()), }) } // ApplyOutboundNatPolicy applies the sNAT policy in HNS/HCN and configures the given CIDR as an exception. func (n *NetConf) ApplyOutboundNatPolicy(exceptionCIDR string) { if exceptionCIDR == "" { return } toPolicyValue := func(cidr ...string) json.RawMessage { if n.ApiVersion == 2 { return bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": ["%s"]}}`, strings.Join(cidr, `","`)) } return bprintf(`{"Type": "OutBoundNAT", "ExceptionList": ["%s"]}`, strings.Join(cidr, `","`)) } exceptionCIDRBytes := []byte(exceptionCIDR) // find OutBoundNAT policy for i := range n.Policies { p := &n.Policies[i] if !strings.EqualFold(p.Name, "EndpointPolicy") { continue } // filter OutBoundNAT policy typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type") if typeValue != "OutBoundNAT" { continue } // parse exception CIDR list var ( exceptionsValue []byte dt jsonparser.ValueType ) if n.ApiVersion == 2 { exceptionsValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "Exceptions") } else { exceptionsValue, dt, _, _ = jsonparser.Get(p.Value, "ExceptionList") } // skip if Exceptions/ExceptionList field is not found if dt == jsonparser.NotExist { continue } // return if found the given CIDR if dt == jsonparser.Array { var found bool _, _ = jsonparser.ArrayEach(exceptionsValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { if dataType == jsonparser.String && len(value) != 0 { if bytes.Compare(value, exceptionCIDRBytes) == 0 { found = true } } }) if found { return } } } // or add a new OutBoundNAT if not found n.Policies = append(n.Policies, Policy{ Name: "EndpointPolicy", Value: toPolicyValue(exceptionCIDR), }) } // ApplyDefaultPAPolicy applies an endpoint PA policy in HNS/HCN. func (n *NetConf) ApplyDefaultPAPolicy(address string) { if address == "" { return } toPolicyValue := func(addr string) json.RawMessage { if n.ApiVersion == 2 { return bprintf(`{"Type": "ProviderAddress", "Settings": {"ProviderAddress": "%s"}}`, addr) } return bprintf(`{"Type": "PA", "PA": "%s"}`, addr) } addressBytes := []byte(address) // find ProviderAddress policy for i := range n.Policies { p := &n.Policies[i] if !strings.EqualFold(p.Name, "EndpointPolicy") { continue } // filter ProviderAddress policy typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type") if typeValue != "PA" && typeValue != "ProviderAddress" { continue } // parse provider address var ( paValue []byte dt jsonparser.ValueType ) if n.ApiVersion == 2 { paValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "ProviderAddress") } else { paValue, dt, _, _ = jsonparser.Get(p.Value, "PA") } // skip if ProviderAddress/PA field is not found if dt == jsonparser.NotExist { continue } // return if found the given address if dt == jsonparser.String && bytes.Compare(paValue, addressBytes) == 0 { return } } // or add a new ProviderAddress if not found n.Policies = append(n.Policies, Policy{ Name: "EndpointPolicy", Value: toPolicyValue(address), }) } // ApplyPortMappingPolicy applies the host/container port mapping policies in HNS/HCN. func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) { if len(portMappings) == 0 { return } toPolicyValue := func(p *PortMapEntry) json.RawMessage { if n.ApiVersion == 2 { var protocolEnum, _ = p.GetProtocolEnum() return bprintf(`{"Type": "PortMapping", "Settings": {"InternalPort": %d, "ExternalPort": %d, "Protocol": %d, "VIP": "%s"}}`, p.ContainerPort, p.HostPort, protocolEnum, p.HostIP) } return bprintf(`{"Type": "NAT", "InternalPort": %d, "ExternalPort": %d, "Protocol": "%s"}`, p.ContainerPort, p.HostPort, p.Protocol) } for i := range portMappings { p := &portMappings[i] // skip the invalid protocol mapping if _, err := p.GetProtocolEnum(); err != nil { continue } n.Policies = append(n.Policies, Policy{ Name: "EndpointPolicy", Value: toPolicyValue(p), }) } } // bprintf is similar to fmt.Sprintf and returns a byte array as result. func bprintf(format string, a ...interface{}) []byte { return []byte(fmt.Sprintf(format, a...)) }