Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 123 additions & 3 deletions plugins/meta/flannel/flannel.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import (
"net"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"

"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
"math/big"
)

const (
Expand All @@ -42,9 +44,10 @@ const (

type NetConf struct {
types.NetConf
SubnetFile string `json:"subnetFile"`
DataDir string `json:"dataDir"`
Delegate map[string]interface{} `json:"delegate"`
SubnetFile string `json:"subnetFile"`
DataDir string `json:"dataDir"`
Delegate map[string]interface{} `json:"delegate"`
WindowsDelegate map[string]interface{} `json:"windowsDelegate"`
}

type subnetEnv struct {
Expand Down Expand Up @@ -188,6 +191,10 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}

if runtime.GOOS == "windows" {
return cmdAddWindows(args.ContainerID, n, fenv)
}

if n.Delegate == nil {
n.Delegate = make(map[string]interface{})
} else {
Expand Down Expand Up @@ -241,6 +248,119 @@ func cmdAdd(args *skel.CmdArgs) error {
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
}

func cmdAddWindows(containerID string, n *NetConf, fenv *subnetEnv) error {
if n.WindowsDelegate == nil {
n.WindowsDelegate = make(map[string]interface{})
} else {
if hasKey(n.WindowsDelegate, "type") && !isString(n.WindowsDelegate["type"]) {
return fmt.Errorf("'windowsDelegate' dictionary, if present, must have (string) 'type' field")
}
if hasKey(n.WindowsDelegate, "name") {
return fmt.Errorf("'windowsDelegate' dictionary must not have 'name' field, it'll be set by flannel")
}
if hasKey(n.WindowsDelegate, "ipam") {
return fmt.Errorf("'windowsDelegate' dictionary must not have 'ipam' field, it'll be set by flannel")
}
}

n.WindowsDelegate["name"] = n.Name

if !hasKey(n.WindowsDelegate, "type") {
n.WindowsDelegate["type"] = "wincni.exe"
}

updateOutboundNat(&n.WindowsDelegate, fenv)

n.WindowsDelegate["cniVersion"] = "0.2.0"
if n.CNIVersion != "" {
n.WindowsDelegate["cniVersion"] = n.CNIVersion
}

n.WindowsDelegate["ipam"] = map[string]interface{}{
"subnet": fenv.sn.String(),
"routes": []interface{}{
map[string]interface{}{
"GW": calcGatewayIPforWindows(fenv.sn),
},
},
}

return delegateAdd(containerID, n.DataDir, n.WindowsDelegate)
}

func updateOutboundNat(windowsDelegate *map[string]interface{}, fenv *subnetEnv) {
if !*fenv.ipmasq {
return
}

if !hasKey(*windowsDelegate, "AdditionalArgs") {
(*windowsDelegate)["AdditionalArgs"] = []interface{}{}
}
addlArgs := (*windowsDelegate)["AdditionalArgs"].([]interface{})
nwToNat := fenv.nw.String()
for _, policy := range addlArgs {
pt := policy.(map[string]interface{})
if hasKey(pt, "Value") {
policyValue := pt["Value"]
switch pv := policyValue.(type) {
case map[string]interface{}:
if hasKey(pv, "Type") {
if strings.EqualFold(pv["Type"].(string), "OutBoundNAT") {
if !hasKey(pv, "ExceptionList") {
// add the exception since there weren't any
pv["ExceptionList"] = []interface{}{nwToNat}
return
}

nets := pv["ExceptionList"].([]interface{})
for _, net := range nets {
if net.(string) == nwToNat {
// found it - do nothing
return
}
}

// its not in the list of exceptions, add it
pv["ExceptionList"] = append(nets, nwToNat)
return
}
}
}
}
}

// didn't find the policy, add it
natEntry := map[string]interface{}{
"Name": "EndpointPolicy",
"Value": map[string]interface{}{
"Type": "OutBoundNAT",
"ExceptionList": []interface{}{
nwToNat,
},
},
}
(*windowsDelegate)["AdditionalArgs"] = append(addlArgs, natEntry)
}

func calcGatewayIPforWindows(ipn *net.IPNet) net.IP {
// HNS currently requires x.x.x.2
// TODO: rakesh: file a bug somewhere to remove this when HNS the HNS limitation is fixed
nid := ipn.IP.Mask(ipn.Mask)
i := ipToInt(nid)
return intToIP(i.Add(i, big.NewInt(2)))
}

func ipToInt(ip net.IP) *big.Int {
if v := ip.To4(); v != nil {
return big.NewInt(0).SetBytes(v)
}
return big.NewInt(0).SetBytes(ip.To16())
}

func intToIP(i *big.Int) net.IP {
return net.IP(i.Bytes())
}

func cmdDel(args *skel.CmdArgs) error {
nc, err := loadFlannelNetConf(args.StdinData)
if err != nil {
Expand Down
86 changes: 86 additions & 0 deletions plugins/meta/flannel/flannel_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2015 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 main

import (
"testing"
"net"
"github.com/stretchr/testify/assert"
)

func TestFlannelSetsWindowsOutboundNatWhenNotSet(t *testing.T) {
// given a subnet config that requests ipmasq
_, nw, _ := net.ParseCIDR("192.168.0.0/16")
_, sn, _ := net.ParseCIDR("192.168.10.0/24")
ipmasq := true
fenv := &subnetEnv{
nw: nw,
sn: sn,
ipmasq: &ipmasq,
}

// apply it
delegate := make(map[string]interface{})
updateOutboundNat(&delegate, fenv)

// verify it got applied
assert.True(t, hasKey(delegate, "AdditionalArgs"))

addlArgs := (delegate["AdditionalArgs"]).([]interface{})
assert.Equal(t, 1, len(addlArgs))

policy := addlArgs[0].(map[string]interface{})
assert.True(t, hasKey(policy, "Name"))
assert.True(t, hasKey(policy, "Value"))
assert.Equal(t, "EndpointPolicy", policy["Name"])

value := policy["Value"].(map[string]interface{})
assert.True(t, hasKey(value, "Type"))
assert.True(t, hasKey(value, "ExceptionList"))
assert.Equal(t, "OutBoundNAT", value["Type"])

exceptionList := value["ExceptionList"].([]interface{})
assert.Equal(t, 1, len(exceptionList))
assert.Equal(t, nw.String(), exceptionList[0].(string))
}

func TestFlannelAppendsOutboundNatToExistingPolicy(t *testing.T) {

// given a subnet config that requests ipmasq
_, nw, _ := net.ParseCIDR("192.168.0.0/16")
_, sn, _ := net.ParseCIDR("192.168.10.0/24")
ipmasq := true
fenv := &subnetEnv{
nw: nw,
sn: sn,
ipmasq: &ipmasq,
}

// first set it
delegate := make(map[string]interface{})
updateOutboundNat(&delegate, fenv)

// then attempt to update it
_, nw2, _ := net.ParseCIDR("10.244.0.0/16")
fenv.nw = nw2
updateOutboundNat(&delegate, fenv)

// but it stays the same!
addlArgs := (delegate["AdditionalArgs"]).([]interface{})
policy := addlArgs[0].(map[string]interface{})
value := policy["Value"].(map[string]interface{})
exceptionList := value["ExceptionList"].([]interface{})
assert.Equal(t, nw.String(), exceptionList[0].(string))
assert.Equal(t, nw2.String(), exceptionList[1].(string))
}