Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overlay network support in < 3.16 kernels #821

Merged
merged 1 commit into from
Dec 17, 2015
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
119 changes: 119 additions & 0 deletions drivers/overlay/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package overlay

import (
"fmt"
"sync"

"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
)

const globalChain = "DOCKER-OVERLAY"

var filterOnce sync.Once

func rawIPTables(args ...string) error {
if output, err := iptables.Raw(args...); err != nil {
return fmt.Errorf("unable to add overlay filter: %v", err)
} else if len(output) != 0 {
return fmt.Errorf("unable to add overlay filter: %s", string(output))
}

return nil
}

func setupGlobalChain() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we fail the operation if setupGlobalChain fails ?
Maybe we should check for the presence of the OVERLAY chain and return success.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to check here. If the global chain hasn't been successfully created it will fail when we create the rule inside it in setFilters

if err := rawIPTables("-N", globalChain); err != nil {
logrus.Errorf("could not create global overlay chain: %v", err)
return
}

if err := rawIPTables("-A", globalChain, "-j", "RETURN"); err != nil {
logrus.Errorf("could not install default return chain in the overlay global chain: %v", err)
return
}
}

func setNetworkChain(cname string, remove bool) error {
// Initialize the onetime global overlay chain
filterOnce.Do(setupGlobalChain)

opt := "-N"
// In case of remove, make sure to flush the rules in the chain
if remove {
if err := rawIPTables("-F", cname); err != nil {
return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
}
opt = "-X"
}

if err := rawIPTables(opt, cname); err != nil {
return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
}

if !remove {
if err := rawIPTables("-A", cname, "-j", "DROP"); err != nil {
return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
}
}

return nil
}

func addNetworkChain(cname string) error {
return setNetworkChain(cname, false)
}

func removeNetworkChain(cname string) error {
return setNetworkChain(cname, true)
}

func setFilters(cname, brName string, remove bool) error {
opt := "-I"
if remove {
opt = "-D"
}

// Everytime we set filters for a new subnet make sure to move the global overlay hook to the top of the both the OUTPUT and forward chains
if !remove {
for _, chain := range []string{"OUTPUT", "FORWARD"} {
exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
if exists {
if err := rawIPTables("-D", chain, "-j", globalChain); err != nil {
return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
}
}

if err := rawIPTables("-I", chain, "-j", globalChain); err != nil {
return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
}
}
}

// Insert/Delete the rule to jump to per-bridge chain
exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
if (!remove && !exists) || (remove && exists) {
if err := rawIPTables(opt, globalChain, "-o", brName, "-j", cname); err != nil {
return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
}
}

exists = iptables.Exists(iptables.Filter, cname, "-i", brName, "-j", "ACCEPT")
if (!remove && exists) || (remove && !exists) {
return nil
}

if err := rawIPTables(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
}

return nil
}

func addFilters(cname, brName string) error {
return setFilters(cname, brName, false)
}

func removeFilters(cname, brName string) error {
return setFilters(cname, brName, true)
}
131 changes: 109 additions & 22 deletions drivers/overlay/ov_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net"
"os"
"sync"
"syscall"

Expand All @@ -12,11 +13,17 @@ import (
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/osl"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)

var (
hostMode bool
hostModeOnce sync.Once
)

type networkTable map[string]*network

type subnet struct {
Expand Down Expand Up @@ -87,22 +94,6 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Dat
return nil
}

/* func (d *driver) createNetworkfromStore(nid string) (*network, error) {
n := &network{
id: nid,
driver: d,
endpoints: endpointTable{},
once: &sync.Once{},
subnets: []*subnet{},
}

err := d.store.GetObject(datastore.Key(n.Key()...), n)
if err != nil {
return nil, fmt.Errorf("unable to get network %q from data store, %v", nid, err)
}
return n, nil
}*/

func (d *driver) DeleteNetwork(nid string) error {
if nid == "" {
return fmt.Errorf("invalid network id")
Expand Down Expand Up @@ -171,24 +162,101 @@ func (n *network) destroySandbox() {
}

for _, s := range n.subnets {
if hostMode {
if err := removeFilters(n.id[:12], s.brName); err != nil {
logrus.Warnf("Could not remove overlay filters: %v", err)
}
}

if s.vxlanName != "" {
err := deleteVxlan(s.vxlanName)
if err != nil {
logrus.Warnf("could not cleanup sandbox properly: %v", err)
}
}
}

if hostMode {
if err := removeNetworkChain(n.id[:12]); err != nil {
logrus.Warnf("could not remove network chain: %v", err)
}
}

sbox.Destroy()
n.setSandbox(nil)
}
}

func (n *network) initSubnetSandbox(s *subnet) error {
// create a bridge and vxlan device for this subnet and move it to the sandbox
brName, err := netutils.GenerateIfaceName("bridge", 7)
func setHostMode() {
if os.Getenv("_OVERLAY_HOST_MODE") != "" {
hostMode = true
return
}

err := createVxlan("testvxlan", 1)
if err != nil {
return err
logrus.Errorf("Failed to create testvxlan interface: %v", err)
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please defer deleteVxlan to make sure it is cleaned up properly for all cases ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.


defer deleteVxlan("testvxlan")

path := "/proc/self/ns/net"
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
logrus.Errorf("Failed to open path %s for network namespace for setting host mode: %v", path, err)
return
}
defer f.Close()

nsFD := f.Fd()

iface, err := netlink.LinkByName("testvxlan")
if err != nil {
logrus.Errorf("Failed to get link testvxlan: %v", err)
return
}

// If we are not able to move the vxlan interface to a namespace
// then fallback to host mode
if err := netlink.LinkSetNsFd(iface, int(nsFD)); err != nil {
hostMode = true
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the /proc/self here will be the daemon which is in the host namepace. Does the call to LinkSetNsFd fail even if the target namespace is same as the source ?

Is there a disadvantage in relying on the kernel version check to determine the hostmode ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it fails.

The disadvantage of kernel version check is that it introduces static assumption on which kernel version this feature exists but it will work the wrong way in the following scenarios:

  • Some one backported this enhancement in a previous kernel version
  • Even if the kernel version is correct, moving to namespace may not work in all kernel versions i.e in some other arch, or somebody is using a heavily customized kernel

So checking for the actual functionality is the most robust way.

}

func (n *network) generateVxlanName(s *subnet) string {
return "vx-" + fmt.Sprintf("%06x", n.vxlanID(s)) + "-" + n.id[:5]
}

func (n *network) generateBridgeName(s *subnet) string {
return "ov-" + fmt.Sprintf("%06x", n.vxlanID(s)) + "-" + n.id[:5]
}

func isOverlap(nw *net.IPNet) bool {
var nameservers []string

if rc, err := resolvconf.Get(); err == nil {
nameservers = resolvconf.GetNameserversAsCIDR(rc.Content)
}

if err := netutils.CheckNameserverOverlaps(nameservers, nw); err != nil {
return true
}

if err := netutils.CheckRouteOverlaps(nw); err != nil {
return true
}

return false
}

func (n *network) initSubnetSandbox(s *subnet) error {
if hostMode && isOverlap(s.subnetIP) {
return fmt.Errorf("overlay subnet %s has conflicts in the host while running in host mode", s.subnetIP.String())
}

// create a bridge and vxlan device for this subnet and move it to the sandbox
brName := n.generateBridgeName(s)
sbox := n.sandbox()

if err := sbox.AddInterface(brName, "br",
Expand All @@ -197,7 +265,12 @@ func (n *network) initSubnetSandbox(s *subnet) error {
return fmt.Errorf("bridge creation in sandbox failed for subnet %q: %v", s.subnetIP.String(), err)
}

vxlanName, err := createVxlan(n.vxlanID(s))
vxlanName := n.generateVxlanName(s)

// Try to delete the vxlan interface if already present
deleteVxlan(vxlanName)

err := createVxlan(vxlanName, n.vxlanID(s))
if err != nil {
return err
}
Expand All @@ -207,6 +280,12 @@ func (n *network) initSubnetSandbox(s *subnet) error {
return fmt.Errorf("vxlan interface creation failed for subnet %q: %v", s.subnetIP.String(), err)
}

if hostMode {
if err := addFilters(n.id[:12], brName); err != nil {
return err
}
}

n.Lock()
s.vxlanName = vxlanName
s.brName = brName
Expand All @@ -220,8 +299,16 @@ func (n *network) initSandbox() error {
n.initEpoch++
n.Unlock()

hostModeOnce.Do(setHostMode)

if hostMode {
if err := addNetworkChain(n.id[:12]); err != nil {
return err
}
}

sbox, err := osl.NewSandbox(
osl.GenerateKey(fmt.Sprintf("%d-", n.initEpoch)+n.id), true)
osl.GenerateKey(fmt.Sprintf("%d-", n.initEpoch)+n.id), !hostMode)
if err != nil {
return fmt.Errorf("could not create network sandbox: %v", err)
}
Expand Down
11 changes: 3 additions & 8 deletions drivers/overlay/ov_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,9 @@ func createVethPair() (string, string, error) {
return name1, name2, nil
}

func createVxlan(vni uint32) (string, error) {
func createVxlan(name string, vni uint32) error {
defer osl.InitOSContext()()

name, err := netutils.GenerateIfaceName("vxlan", 7)
if err != nil {
return "", fmt.Errorf("error generating vxlan name: %v", err)
}

vxlan := &netlink.Vxlan{
LinkAttrs: netlink.LinkAttrs{Name: name},
VxlanId: int(vni),
Expand All @@ -66,10 +61,10 @@ func createVxlan(vni uint32) (string, error) {
}

if err := netlink.LinkAdd(vxlan); err != nil {
return "", fmt.Errorf("error creating vxlan interface: %v", err)
return fmt.Errorf("error creating vxlan interface: %v", err)
}

return name, nil
return nil
}

func deleteVxlan(name string) error {
Expand Down
Loading