Skip to content

Commit

Permalink
Add windows vxlan
Browse files Browse the repository at this point in the history
    - patch for flannel-io#922
  • Loading branch information
thxCode committed Sep 12, 2018
1 parent 72ba6b7 commit 341b387
Show file tree
Hide file tree
Showing 5 changed files with 544 additions and 6 deletions.
79 changes: 79 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
# config.vm.box = "ubuntu/xenial64"

# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
# config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.

# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# apt-get update
# apt-get install -y apache2
# SHELL

config.vm.box = "maiwj/ubuntu-xenial64"
config.vm.synced_folder "..", "/go/src/github.com/coreos"
config.vm.provider "virtualbox" do |vb|
vb.name = "flanneld-package"
vb.cpus = 2
vb.memory = 1024
end

end
8 changes: 4 additions & 4 deletions backend/hostgw/hostgw_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (be *HostgwBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup,
if len(cfg.Name) == 0 {
cfg.Name = "cbr0"
}
log.Infof("HOST-GW config: Name=%s DNSServerList=%s", cfg.Name, cfg.DNSServerList)
log.Infof("HOST-GW config: %+v", cfg)

n := &backend.RouteNetwork{
SimpleNetwork: backend.SimpleNetwork{
Expand Down Expand Up @@ -226,7 +226,7 @@ func (be *HostgwBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup,
if err != nil {
return nil, errors.Annotatef(err, "failed to find interface for IP Address %s", interfaceIpAddress)
}
log.Infof("Found %v NetshInterface with IP %s", netInterface, interfaceIpAddress)
log.Infof("Found %+v interface with IP %s", netInterface, interfaceIpAddress)

// When a new hns network is created, the interface is modified, esp the name, index
if expectedNetwork.ManagementIP == netInterface.IpAddress {
Expand All @@ -236,9 +236,9 @@ func (be *HostgwBackend) RegisterNetwork(ctx context.Context, wg sync.WaitGroup,

interfaceIdx := strconv.Itoa(netInterface.Idx)
if err := netshHelper.EnableForwarding(interfaceIdx); err != nil {
return nil, errors.Annotatef(err, "failed to enable forwarding on %s index %d", netInterface.Name, interfaceIdx)
return nil, errors.Annotatef(err, "failed to enable forwarding on %s index %s", netInterface.Name, interfaceIdx)
}
log.Infof("Enabled forwarding on %s index %d", netInterface.Name, interfaceIdx)
log.Infof("Enabled forwarding on %s index %s", netInterface.Name, interfaceIdx)
}

return n, nil
Expand Down
223 changes: 223 additions & 0 deletions backend/vxlan/device_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2018 flannel 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 vxlan

import (
"encoding/json"
"fmt"
"github.com/Microsoft/hcsshim"
"github.com/buger/jsonparser"
"github.com/coreos/flannel/pkg/ip"
log "github.com/golang/glog"
"github.com/juju/errors"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/coreos/flannel/network/netsh"
utilexec "k8s.io/utils/exec"
)

type vxlanDeviceAttrs struct {
vni uint32
name string
gbp bool
addressPrefix ip.IP4Net
}

type vxlanDevice struct {
link *hcsshim.HNSNetwork
macPrefix string
directRouting bool
}

func newVXLANDevice(devAttrs *vxlanDeviceAttrs) (*vxlanDevice, error) {
hnsNetwork := &hcsshim.HNSNetwork{
Name: devAttrs.name,
Type: "Overlay",
Subnets: make([]hcsshim.Subnet, 0, 1),
}

hnsNetwork, err := ensureNetwork(hnsNetwork, int64(devAttrs.vni), devAttrs.addressPrefix.String(), (devAttrs.addressPrefix.IP + 1).String())
if err != nil {
return nil, err
}

return &vxlanDevice{
link: hnsNetwork,
}, nil
}

func ensureNetwork(expectedNetwork *hcsshim.HNSNetwork, expectedVSID int64, expectedAddressPrefix, expectedGW string) (*hcsshim.HNSNetwork, error) {
createNetwork := true
networkName := expectedNetwork.Name

// 1. Check if the HNSNetwork exists and has the expected settings
existingNetwork, err := hcsshim.GetHNSNetworkByName(networkName)
if err == nil {
if existingNetwork.Type == expectedNetwork.Type {
for _, existingSubnet := range existingNetwork.Subnets {
if existingSubnet.AddressPrefix == expectedAddressPrefix && existingSubnet.GatewayAddress == expectedGW {
createNetwork = false
log.Infof("Found existing HNSNetwork %s", networkName)
break
}
}
}
}

// 2. Create a new HNSNetwork
if createNetwork {
if existingNetwork != nil {
if _, err := existingNetwork.Delete(); err != nil {
return nil, errors.Annotatef(err, "failed to delete existing HNSNetwork %s", networkName)
}
log.Infof("Deleted stale HNSNetwork %s", networkName)
}

// Add a VxLan subnet
expectedNetwork.Subnets = append(expectedNetwork.Subnets, hcsshim.Subnet{
AddressPrefix: expectedAddressPrefix,
GatewayAddress: expectedGW,
Policies: []json.RawMessage{
[]byte(fmt.Sprintf(`{"Type":"VSID","VSID":%d}`, expectedVSID)),
},
})

// Config request params
jsonRequest, err := json.Marshal(expectedNetwork)
if err != nil {
return nil, errors.Annotatef(err, "failed to marshal %+v", expectedNetwork)
}

log.Infof("Attempting to create HNSNetwork %s", string(jsonRequest))
newNetwork, err := hcsshim.HNSNetworkRequest("POST", "", string(jsonRequest))
if err != nil {
return nil, errors.Annotatef(err, "failed to create HNSNetwork %s", networkName)
}

var waitErr, lastErr error
// Wait for the network to populate Management IP
waitErr = wait.Poll(1*time.Second, 10*time.Second, func() (done bool, err error) {
newNetwork, lastErr = hcsshim.HNSNetworkRequest("GET", newNetwork.Id, "")
return newNetwork != nil && len(newNetwork.ManagementIP) == 0, nil
})
if waitErr == wait.ErrWaitTimeout {
return nil, errors.Annotatef(lastErr, "timeout, failed to get management IP from HNSNetwork by ID: %s", newNetwork.Id)
}

// Wait for the interface with the management IP
netshHelper := netsh.NewHelper(utilexec.New())
waitErr = wait.Poll(1*time.Second, 10*time.Second, func() (done bool, err error) {
_, lastErr = netshHelper.GetInterfaceByIP(newNetwork.ManagementIP)
return lastErr == nil, nil
})
if waitErr == wait.ErrWaitTimeout {
return nil, errors.Annotatef(lastErr, "timeout, failed to get net interface by IP: %s", newNetwork.ManagementIP)
}

log.Infof("Created HNSNetwork %s", networkName)
return newNetwork, nil
}

return existingNetwork, nil
}

type neighbor struct {
MAC string
IP ip.IP4
ManagementAddress string
}

func (dev *vxlanDevice) AddEndpoint(n *neighbor) error {
endpointName := createEndpointName(n.IP)

// 1. Check if the HNSEndpoint exists and has the expected settings
existingEndpoint, err := hcsshim.GetHNSEndpointByName(endpointName)
if err == nil && existingEndpoint.VirtualNetwork == dev.link.Id {
// Check policies if there is PA type
targetType := "PA"
for _, policy := range existingEndpoint.Policies {
policyType, _ := jsonparser.GetUnsafeString(policy, "Type")
if policyType == targetType {
actualPaIP, _ := jsonparser.GetUnsafeString(policy, targetType)
if actualPaIP == n.ManagementAddress {
log.Infof("Found existing remote HNSEndpoint %s", endpointName)
return nil
}
}
}
}

// 2. Create a new HNSNetwork
if existingEndpoint != nil {
if _, err := existingEndpoint.Delete(); err != nil {
return errors.Annotatef(err, "failed to delete existing remote HNSEndpoint %s", endpointName)
}
log.V(4).Infof("Deleted stale HNSEndpoint %s", endpointName)
}

newEndpoint := &hcsshim.HNSEndpoint{
Id: "",
Name: endpointName,
IPAddress: n.IP.ToIP(),
MacAddress: n.MAC,
VirtualNetwork: dev.link.Id,
IsRemoteEndpoint: true,
Policies: []json.RawMessage{
[]byte(fmt.Sprintf(`{"Type":"PA","PA":"%s"}`, n.ManagementAddress)),
},
}
if _, err := newEndpoint.Create(); err != nil {
return errors.Annotatef(err, "failed to create remote HNSEndpoint %s", endpointName)
}
log.V(4).Infof("Created HNSEndpoint %s", endpointName)

return nil
}

func (dev *vxlanDevice) DelEndpoint(n *neighbor) error {
endpointName := createEndpointName(n.IP)

existingEndpoint, err := hcsshim.GetHNSEndpointByName(endpointName)
if err == nil && existingEndpoint.VirtualNetwork == dev.link.Id {
// Check policies if there is PA type
targetType := "PA"
for _, policy := range existingEndpoint.Policies {
policyType, _ := jsonparser.GetUnsafeString(policy, "Type")
if policyType == targetType {
actualPaIP, _ := jsonparser.GetUnsafeString(policy, targetType)
if actualPaIP == n.ManagementAddress {
// Found it and delete
if _, err := existingEndpoint.Delete(); err != nil {
return errors.Annotatef(err, "failed to delete remote HNSEndpoint %s", endpointName)
}

log.V(4).Infof("Deleted HNSEndpoint %s", endpointName)
break
}
}
}
}

return nil
}

func (dev *vxlanDevice) ConjureMac(targetIP ip.IP4) string {
a, b, c, d := targetIP.Octets()
return fmt.Sprintf("%v-%02x-%02x-%02x-%02x", dev.macPrefix, a, b, c, d)
}

func createEndpointName(targetIP ip.IP4) string {
return "remote_" + targetIP.String()
}
Loading

0 comments on commit 341b387

Please sign in to comment.