
- support v2 api - unify v1 and v2 api BREAKING CHANGE: - remove `HcnPolicyArgs` field - merge `HcnPolicyArgs` into `Policies` field Signed-off-by: thxcode <thxcode0824@gmail.com>
349 lines
9.7 KiB
Go
349 lines
9.7 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 (
|
|
"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...))
|
|
}
|