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 24, 2018
1 parent 6a83e63 commit 65fb2a5
Show file tree
Hide file tree
Showing 4 changed files with 474 additions and 6 deletions.
9 changes: 5 additions & 4 deletions Documentation/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ Use in-kernel VXLAN to encapsulate the packets.

Type and options:
* `Type` (string): `vxlan`
* `VNI` (number): VXLAN Identifier (VNI) to be used. Defaults to 1.
* `Port` (number): UDP port to use for sending encapsulated packets. Defaults to kernel default, currently 8472.
* `GBP` (Boolean): Enable [VXLAN Group Based Policy](https://github.com/torvalds/linux/commit/3511494ce2f3d3b77544c79b87511a4ddb61dc89). Defaults to `false`.
* `DirectRouting` (Boolean): Enable direct routes (like `host-gw`) when the hosts are on the same subnet. VXLAN will only be used to encapsulate packets to hosts on different subnets. Defaults to `false`.
* `VNI` (number): VXLAN Identifier (VNI) to be used. On Linux, defaults to 1. On Windows should be greater than or equal to 4096.
* `Port` (number): UDP port to use for sending encapsulated packets. On Linux, defaults to kernel default, currently 8472, but on Windows, must be 4789.
* `GBP` (Boolean): Enable [VXLAN Group Based Policy](https://github.com/torvalds/linux/commit/3511494ce2f3d3b77544c79b87511a4ddb61dc89). Defaults to `false`. GBP is not supported on Windows
* `DirectRouting` (Boolean): Enable direct routes (like `host-gw`) when the hosts are on the same subnet. VXLAN will only be used to encapsulate packets to hosts on different subnets. Defaults to `false`. DirectRouting is not supported on Windows.
* `MacPrefix` (String): Only use on Windows, set to the MAC prefix. Defaults to `0E-2A`.

### host-gw

Expand Down
224 changes: 224 additions & 0 deletions backend/vxlan/device_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// 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/rakelkar/gonetsh/netsh"
"github.com/coreos/flannel/pkg/ip"
log "github.com/golang/glog"
"github.com/juju/errors"
"k8s.io/apimachinery/pkg/util/wait"
utilexec "k8s.io/utils/exec"
"time"
)

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
log.Infof("Waiting to get ManagementIP from HNSNetwork %s", networkName)
waitErr = wait.Poll(500*time.Millisecond, 5*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 %s", networkName)
}

// Wait for the interface with the management IP
netshHelper := netsh.New(utilexec.New())
log.Infof("Waiting to get net interface for HNSNetwork %s (%s)", networkName, newNetwork.ManagementIP)
waitErr = wait.Poll(500*time.Millisecond, 5*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 for HNSNetwork %s (%s)", networkName, 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{
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()
}
125 changes: 125 additions & 0 deletions backend/vxlan/vxlan_network_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2015 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 (
log "github.com/golang/glog"
"golang.org/x/net/context"
"sync"

"github.com/coreos/flannel/backend"
"github.com/coreos/flannel/subnet"

"github.com/coreos/flannel/pkg/ip"
"strings"
)

type network struct {
backend.SimpleNetwork
dev *vxlanDevice
subnetMgr subnet.Manager
}

const (
encapOverhead = 50
)

func newNetwork(subnetMgr subnet.Manager, extIface *backend.ExternalInterface, dev *vxlanDevice, _ ip.IP4Net, lease *subnet.Lease) (*network, error) {
nw := &network{
SimpleNetwork: backend.SimpleNetwork{
SubnetLease: lease,
ExtIface: extIface,
},
subnetMgr: subnetMgr,
dev: dev,
}

return nw, nil
}

func (nw *network) Run(ctx context.Context) {
wg := sync.WaitGroup{}

log.V(0).Info("Watching for new subnet leases")
events := make(chan []subnet.Event)
wg.Add(1)
go func() {
subnet.WatchLeases(ctx, nw.subnetMgr, nw.SubnetLease, events)
log.V(1).Info("WatchLeases exited")
wg.Done()
}()

defer wg.Wait()

for {
select {
case evtBatch := <-events:
nw.handleSubnetEvents(evtBatch)

case <-ctx.Done():
return
}
}
}

func (nw *network) MTU() int {
return nw.ExtIface.Iface.MTU - encapOverhead
}

func (nw *network) handleSubnetEvents(batch []subnet.Event) {
for _, event := range batch {
leaseSubnet := event.Lease.Subnet
leaseAttrs := event.Lease.Attrs
if !strings.EqualFold(leaseAttrs.BackendType, "vxlan") {
log.Warningf("ignoring non-vxlan subnet(%v): type=%v", leaseSubnet, leaseAttrs.BackendType)
continue
}

publicIP := leaseAttrs.PublicIP.String()
remoteIP := leaseSubnet.IP + 2
lastIP := leaseSubnet.Next().IP - 1

switch event.Type {
case subnet.EventAdded:
for ; remoteIP < lastIP; remoteIP++ {
n := &neighbor{
IP: remoteIP,
MAC: nw.dev.ConjureMac(remoteIP),
ManagementAddress: publicIP,
}

log.V(2).Infof("adding subnet: %v publicIP: %s vtepMAC: %s", leaseSubnet, n.ManagementAddress, n.MAC)
if err := nw.dev.AddEndpoint(n); err != nil {
log.Error(err)
}
}
case subnet.EventRemoved:
for ; remoteIP < lastIP; remoteIP++ {
n := &neighbor{
IP: remoteIP,
MAC: nw.dev.ConjureMac(remoteIP),
ManagementAddress: publicIP,
}

log.V(2).Infof("removing subnet: %v publicIP: %s vtepMAC: %s", leaseSubnet, n.ManagementAddress, n.MAC)
if err := nw.dev.DelEndpoint(n); err != nil {
log.Error(err)
}
}
default:
log.Error("internal error: unknown event type: ", int(event.Type))
}
}
}
Loading

0 comments on commit 65fb2a5

Please sign in to comment.