dotcloud/docker

View on GitHub
libnetwork/osl/neigh_linux.go

Summary

Maintainability
A
1 hr
Test Coverage
package osl

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "net"
    "os"

    "github.com/containerd/log"
    "github.com/vishvananda/netlink"
)

// NeighborSearchError indicates that the neighbor is already present
type NeighborSearchError struct {
    ip      net.IP
    mac     net.HardwareAddr
    present bool
}

func (n NeighborSearchError) Error() string {
    return fmt.Sprintf("Search neighbor failed for IP %v, mac %v, present in db:%t", n.ip, n.mac, n.present)
}

type neigh struct {
    dstIP    net.IP
    dstMac   net.HardwareAddr
    linkName string
    linkDst  string
    family   int
}

func (n *Namespace) findNeighbor(dstIP net.IP, dstMac net.HardwareAddr) *neigh {
    n.mu.Lock()
    defer n.mu.Unlock()

    for _, nh := range n.neighbors {
        if nh.dstIP.Equal(dstIP) && bytes.Equal(nh.dstMac, dstMac) {
            return nh
        }
    }

    return nil
}

// DeleteNeighbor deletes neighbor entry from the sandbox.
func (n *Namespace) DeleteNeighbor(dstIP net.IP, dstMac net.HardwareAddr) error {
    nh := n.findNeighbor(dstIP, dstMac)
    if nh == nil {
        return NeighborSearchError{dstIP, dstMac, false}
    }

    n.mu.Lock()
    nlh := n.nlHandle
    n.mu.Unlock()

    var linkIndex int
    if nh.linkDst != "" {
        iface, err := nlh.LinkByName(nh.linkDst)
        if err != nil {
            return fmt.Errorf("could not find interface with destination name %s: %v", nh.linkDst, err)
        }
        linkIndex = iface.Attrs().Index
    }

    nlnh := &netlink.Neigh{
        LinkIndex: linkIndex,
        IP:        dstIP,
        State:     netlink.NUD_PERMANENT,
        Family:    nh.family,
    }

    if nh.family > 0 {
        nlnh.HardwareAddr = dstMac
        nlnh.Flags = netlink.NTF_SELF
    }

    // If the kernel deletion fails for the neighbor entry still remove it
    // from the namespace cache, otherwise kernel update can fail if the
    // neighbor moves back to the same host again.
    if err := nlh.NeighDel(nlnh); err != nil && !errors.Is(err, os.ErrNotExist) {
        log.G(context.TODO()).Warnf("Deleting neighbor IP %s, mac %s failed, %v", dstIP, dstMac, err)
    }

    // Delete the dynamic entry in the bridge
    if nh.family > 0 {
        if err := nlh.NeighDel(&netlink.Neigh{
            LinkIndex:    linkIndex,
            IP:           dstIP,
            Family:       nh.family,
            HardwareAddr: dstMac,
            Flags:        netlink.NTF_MASTER,
        }); err != nil && !errors.Is(err, os.ErrNotExist) {
            log.G(context.TODO()).WithError(err).Warn("error while deleting neighbor entry")
        }
    }

    n.mu.Lock()
    for i, neighbor := range n.neighbors {
        if neighbor.dstIP.Equal(dstIP) && bytes.Equal(neighbor.dstMac, dstMac) {
            n.neighbors = append(n.neighbors[:i], n.neighbors[i+1:]...)
            break
        }
    }
    n.mu.Unlock()
    log.G(context.TODO()).Debugf("Neighbor entry deleted for IP %v, mac %v", dstIP, dstMac)

    return nil
}

// AddNeighbor adds a neighbor entry into the sandbox.
func (n *Namespace) AddNeighbor(dstIP net.IP, dstMac net.HardwareAddr, force bool, options ...NeighOption) error {
    var (
        iface                  netlink.Link
        err                    error
        neighborAlreadyPresent bool
    )

    // If the namespace already has the neighbor entry but the AddNeighbor is called
    // because of a miss notification (force flag) program the kernel anyway.
    nh := n.findNeighbor(dstIP, dstMac)
    if nh != nil {
        neighborAlreadyPresent = true
        log.G(context.TODO()).Warnf("Neighbor entry already present for IP %v, mac %v neighbor:%+v forceUpdate:%t", dstIP, dstMac, nh, force)
        if !force {
            return NeighborSearchError{dstIP, dstMac, true}
        }
    }

    nh = &neigh{
        dstIP:  dstIP,
        dstMac: dstMac,
    }

    nh.processNeighOptions(options...)

    if nh.linkName != "" {
        nh.linkDst = n.findDst(nh.linkName, false)
        if nh.linkDst == "" {
            return fmt.Errorf("could not find the interface with name %s", nh.linkName)
        }
    }

    n.mu.Lock()
    nlh := n.nlHandle
    n.mu.Unlock()

    if nh.linkDst != "" {
        iface, err = nlh.LinkByName(nh.linkDst)
        if err != nil {
            return fmt.Errorf("could not find interface with destination name %s: %v", nh.linkDst, err)
        }
    }

    nlnh := &netlink.Neigh{
        IP:           dstIP,
        HardwareAddr: dstMac,
        State:        netlink.NUD_PERMANENT,
        Family:       nh.family,
    }

    if nlnh.Family > 0 {
        nlnh.Flags = netlink.NTF_SELF
    }

    if nh.linkDst != "" {
        nlnh.LinkIndex = iface.Attrs().Index
    }

    if err := nlh.NeighSet(nlnh); err != nil {
        return fmt.Errorf("could not add neighbor entry:%+v error:%v", nlnh, err)
    }

    if neighborAlreadyPresent {
        return nil
    }

    n.mu.Lock()
    n.neighbors = append(n.neighbors, nh)
    n.mu.Unlock()
    log.G(context.TODO()).Debugf("Neighbor entry added for IP:%v, mac:%v on ifc:%s", dstIP, dstMac, nh.linkName)

    return nil
}