mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Configurable count and interval for gratuitous ARP/NA messages
The default is to send 3 messages at 1s intervals.
That can be overridden in "docker network create" using:
-o com.docker.network.advertise_addr_nmsgs=3
-o com.docker.network.advertise_addr_ms=1000
Or, in daemon.json for each driver:
"default-network-opts": {
"bridge": {
"com.docker.network.advertise_addr_nmsgs": "3",
"com.docker.network.advertise_addr_ms": "1000"
}
}
The allowed range is 0-3 for the number of messages, and
100-2000ms for the interval. Setting nmsgs to 0 disables the
gratuitous ARP/NA messages.
The default bridge will always use the built-in defaults,
it cannot be configured.
Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
@@ -39,6 +39,14 @@ const (
|
||||
// DriverMTU constant represents the MTU size for the network driver
|
||||
DriverMTU = DriverPrefix + ".mtu"
|
||||
|
||||
// AdvertiseAddrNMsgs is the number of unsolicited ARP/NA messages that will be sent to
|
||||
// advertise an interface's IP and MAC addresses.
|
||||
AdvertiseAddrNMsgs = Prefix + ".advertise_addr_nmsgs"
|
||||
|
||||
// AdvertiseAddrIntervalMs is the minimum interval between ARP/NA advertisements for
|
||||
// an interface's IP and MAC addresses (in milliseconds).
|
||||
AdvertiseAddrIntervalMs = Prefix + ".advertise_addr_ms"
|
||||
|
||||
// OverlayVxlanIDList constant represents a list of VXLAN Ids as csv
|
||||
OverlayVxlanIDList = DriverPrefix + ".overlay.vxlanid_list"
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package libnetwork
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -364,7 +365,11 @@ func (n *Network) validateConfiguration() error {
|
||||
"[ ingress | internal | attachable | scope ] are not supported.")
|
||||
}
|
||||
}
|
||||
if n.configFrom != "" {
|
||||
if n.configFrom == "" {
|
||||
if err := n.validateAdvertiseAddrConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if n.configOnly {
|
||||
return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network")
|
||||
}
|
||||
@@ -533,6 +538,35 @@ func (n *Network) getEpCnt() *endpointCnt {
|
||||
return n.epCnt
|
||||
}
|
||||
|
||||
func (n *Network) validateAdvertiseAddrConfig() error {
|
||||
var errs []error
|
||||
_, err := n.validatedAdvertiseAddrNMsgs()
|
||||
errs = append(errs, err)
|
||||
_, err = n.validatedAdvertiseAddrInterval()
|
||||
errs = append(errs, err)
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (n *Network) advertiseAddrNMsgs() (int, bool) {
|
||||
v, err := n.validatedAdvertiseAddrNMsgs()
|
||||
if err != nil || v == nil {
|
||||
// On Linux, config was validated before network creation. This
|
||||
// path is for un-set values and unsupported platforms.
|
||||
return 0, false
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
func (n *Network) advertiseAddrInterval() (time.Duration, bool) {
|
||||
v, err := n.validatedAdvertiseAddrInterval()
|
||||
if err != nil || v == nil {
|
||||
// On Linux, config was validated before network creation. This
|
||||
// path is for un-set values and unsupported platforms.
|
||||
return 0, false
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
|
||||
func (n *Network) MarshalJSON() ([]byte, error) {
|
||||
netMap := make(map[string]interface{})
|
||||
|
||||
@@ -4,6 +4,12 @@ package libnetwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"github.com/docker/docker/libnetwork/osl"
|
||||
|
||||
"github.com/docker/docker/libnetwork/ipams/defaultipam"
|
||||
)
|
||||
@@ -32,3 +38,36 @@ func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers [
|
||||
func defaultIpamForNetworkType(networkType string) string {
|
||||
return defaultipam.DriverName
|
||||
}
|
||||
|
||||
func (n *Network) validatedAdvertiseAddrNMsgs() (*int, error) {
|
||||
nMsgsStr, ok := n.DriverOptions()[netlabel.AdvertiseAddrNMsgs]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
nMsgs, err := strconv.Atoi(nMsgsStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value for option "+netlabel.AdvertiseAddrNMsgs+" %q must be an integer", nMsgsStr)
|
||||
}
|
||||
if nMsgs < osl.AdvertiseAddrNMsgsMin || nMsgs > osl.AdvertiseAddrNMsgsMax {
|
||||
return nil, fmt.Errorf(netlabel.AdvertiseAddrNMsgs+" must be in the range %d to %d",
|
||||
osl.AdvertiseAddrNMsgsMin, osl.AdvertiseAddrNMsgsMax)
|
||||
}
|
||||
return &nMsgs, nil
|
||||
}
|
||||
|
||||
func (n *Network) validatedAdvertiseAddrInterval() (*time.Duration, error) {
|
||||
intervalStr, ok := n.DriverOptions()[netlabel.AdvertiseAddrIntervalMs]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
msecs, err := strconv.Atoi(intervalStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value for option "+netlabel.AdvertiseAddrIntervalMs+" %q must be integer milliseconds", intervalStr)
|
||||
}
|
||||
interval := time.Duration(msecs) * time.Millisecond
|
||||
if interval < osl.AdvertiseAddrIntervalMin || interval > osl.AdvertiseAddrIntervalMax {
|
||||
return nil, fmt.Errorf(netlabel.AdvertiseAddrIntervalMs+" must be in the range %d to %d",
|
||||
osl.AdvertiseAddrIntervalMin/time.Millisecond, osl.AdvertiseAddrIntervalMax/time.Millisecond)
|
||||
}
|
||||
return &interval, nil
|
||||
}
|
||||
|
||||
@@ -240,3 +240,11 @@ func defaultIpamForNetworkType(networkType string) string {
|
||||
}
|
||||
return defaultipam.DriverName
|
||||
}
|
||||
|
||||
func (n *Network) validatedAdvertiseAddrNMsgs() (*int, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *Network) validatedAdvertiseAddrInterval() (*time.Duration, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -25,14 +25,45 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// AdvertiseAddrNMsgsMin defines the minimum number of ARP/NA messages sent when an
|
||||
// interface is configured.
|
||||
// Zero can be used to disable unsolicited ARP/NA.
|
||||
AdvertiseAddrNMsgsMin = 0
|
||||
// AdvertiseAddrNMsgsMax defines the maximum number of ARP/NA messages sent when an
|
||||
// interface is configured. It's three, to match RFC-5227 Section 1.1
|
||||
// // ("PROBE_NUM=3") and RFC-4861 MAX_NEIGHBOR_ADVERTISEMENT.
|
||||
AdvertiseAddrNMsgsMax = 3
|
||||
// advertiseAddrNMsgsDefault is the default number of ARP/NA messages sent when
|
||||
// an interface is configured.
|
||||
advertiseAddrNMsgsDefault = 3
|
||||
|
||||
// AdvertiseAddrIntervalMin defines the minimum interval between ARP/NA messages
|
||||
// sent when an interface is configured. The min defined here is nonstandard,
|
||||
// RFC-5227 PROBE_MIN and the default for RetransTimer in RFC-4861 are one
|
||||
// second. But, faster resends may be useful in a bridge network (where packets
|
||||
// are not transmitted on a real network).
|
||||
AdvertiseAddrIntervalMin = 100 * time.Millisecond
|
||||
// AdvertiseAddrIntervalMax defines the maximum interval between ARP/NA messages
|
||||
// sent when an interface is configured. The max of 2s matches RFC-5227
|
||||
// PROBE_MAX.
|
||||
AdvertiseAddrIntervalMax = 2 * time.Second
|
||||
// advertiseAddrIntervalDefault is the default interval between ARP/NA messages
|
||||
// sent when and interface is configured.
|
||||
// One second matches RFC-5227 PROBE_MIN and the default for RetransTimer in RFC-4861.
|
||||
advertiseAddrIntervalDefault = time.Second
|
||||
)
|
||||
|
||||
// newInterface creates a new interface in the given namespace using the
|
||||
// provided options.
|
||||
func newInterface(ns *Namespace, srcName, dstPrefix string, options ...IfaceOption) (*Interface, error) {
|
||||
i := &Interface{
|
||||
stopCh: make(chan struct{}),
|
||||
srcName: srcName,
|
||||
dstName: dstPrefix,
|
||||
ns: ns,
|
||||
stopCh: make(chan struct{}),
|
||||
srcName: srcName,
|
||||
dstName: dstPrefix,
|
||||
advertiseAddrNMsgs: advertiseAddrNMsgsDefault,
|
||||
advertiseAddrInterval: advertiseAddrIntervalDefault,
|
||||
ns: ns,
|
||||
}
|
||||
for _, opt := range options {
|
||||
if opt != nil {
|
||||
@@ -69,7 +100,13 @@ type Interface struct {
|
||||
routes []*net.IPNet
|
||||
bridge bool
|
||||
sysctls []string
|
||||
ns *Namespace
|
||||
// advertiseAddrNMsgs is the number of unsolicited ARP/NA messages that will be sent to
|
||||
// advertise the interface's addresses. No messages will be sent if this is zero.
|
||||
advertiseAddrNMsgs int
|
||||
// advertiseAddrInterval is the interval between unsolicited ARP/NA messages sent to
|
||||
// advertise the interface's addresses.
|
||||
advertiseAddrInterval time.Duration
|
||||
ns *Namespace
|
||||
}
|
||||
|
||||
// SrcName returns the name of the interface in the origin network namespace.
|
||||
@@ -382,7 +419,7 @@ func waitForIfUpped(ctx context.Context, ns netns.NsHandle, ifIndex int) (bool,
|
||||
}
|
||||
}
|
||||
|
||||
// advertiseAddrs triggers send gratuitous ARP and Neighbour Advertisement
|
||||
// advertiseAddrs triggers send unsolicited ARP and Neighbour Advertisement
|
||||
// messages, so that caches are updated with the MAC address currently associated
|
||||
// with the interface's IP addresses.
|
||||
//
|
||||
@@ -414,6 +451,10 @@ func (n *Namespace) advertiseAddrs(ctx context.Context, ifIndex int, i *Interfac
|
||||
log.G(ctx).Debug("No MAC address to advertise")
|
||||
return nil
|
||||
}
|
||||
if i.advertiseAddrNMsgs == 0 {
|
||||
log.G(ctx).Debug("Unsolicited ARP/NA is disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
arpSender, naSender := n.prepAdvertiseAddrs(ctx, i, ifIndex)
|
||||
if arpSender == nil && naSender == nil {
|
||||
@@ -464,6 +505,9 @@ func (n *Namespace) advertiseAddrs(ctx context.Context, ifIndex int, i *Interfac
|
||||
if err := send(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if i.advertiseAddrNMsgs == 1 {
|
||||
return nil
|
||||
}
|
||||
// Don't clean up on return from this function, there are more ARPs/NAs to send.
|
||||
stillSending = true
|
||||
|
||||
@@ -472,9 +516,9 @@ func (n *Namespace) advertiseAddrs(ctx context.Context, ifIndex int, i *Interfac
|
||||
defer cleanup()
|
||||
ctx, span := otel.Tracer("").Start(context.WithoutCancel(ctx), "libnetwork.osl.advertiseAddrs")
|
||||
defer span.End()
|
||||
ticker := time.NewTicker(time.Second)
|
||||
ticker := time.NewTicker(i.advertiseAddrInterval)
|
||||
defer ticker.Stop()
|
||||
for c := range 2 {
|
||||
for c := range i.advertiseAddrNMsgs - 1 {
|
||||
select {
|
||||
case <-i.stopCh:
|
||||
log.G(ctx).Debug("Unsolicited ARP/NA sends cancelled")
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package osl
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (nh *neigh) processNeighOptions(options ...NeighOption) {
|
||||
for _, opt := range options {
|
||||
@@ -89,3 +93,29 @@ func WithSysctls(sysctls []string) IfaceOption {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAdvertiseAddrNMsgs sets the number of unsolicited ARP/NA messages that will
|
||||
// be sent to advertise a network interface's addresses.
|
||||
func WithAdvertiseAddrNMsgs(nMsgs int) IfaceOption {
|
||||
return func(i *Interface) error {
|
||||
if nMsgs < AdvertiseAddrNMsgsMin || nMsgs > AdvertiseAddrNMsgsMax {
|
||||
return fmt.Errorf("AdvertiseAddrNMsgs %d is not in the range %d to %d",
|
||||
nMsgs, AdvertiseAddrNMsgsMin, AdvertiseAddrNMsgsMax)
|
||||
}
|
||||
i.advertiseAddrNMsgs = nMsgs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAdvertiseAddrInterval sets the interval between unsolicited ARP/NA messages
|
||||
// sent to advertise a network interface's addresses.
|
||||
func WithAdvertiseAddrInterval(interval time.Duration) IfaceOption {
|
||||
return func(i *Interface) error {
|
||||
if interval < AdvertiseAddrIntervalMin || interval > AdvertiseAddrIntervalMax {
|
||||
return fmt.Errorf("AdvertiseAddrNMsgs %d is not in the range %v to %v milliseconds",
|
||||
interval, AdvertiseAddrIntervalMin, AdvertiseAddrIntervalMax)
|
||||
}
|
||||
i.advertiseAddrInterval = interval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +340,14 @@ func (sb *Sandbox) populateNetworkResources(ctx context.Context, ep *Endpoint) e
|
||||
if sysctls := ep.getSysctls(); len(sysctls) > 0 {
|
||||
ifaceOptions = append(ifaceOptions, osl.WithSysctls(sysctls))
|
||||
}
|
||||
if n := ep.getNetwork(); n != nil {
|
||||
if nMsgs, ok := n.advertiseAddrNMsgs(); ok {
|
||||
ifaceOptions = append(ifaceOptions, osl.WithAdvertiseAddrNMsgs(nMsgs))
|
||||
}
|
||||
if interval, ok := n.advertiseAddrInterval(); ok {
|
||||
ifaceOptions = append(ifaceOptions, osl.WithAdvertiseAddrInterval(interval))
|
||||
}
|
||||
}
|
||||
|
||||
if err := sb.osSbox.AddInterface(ctx, i.srcName, i.dstPrefix, ifaceOptions...); err != nil {
|
||||
return fmt.Errorf("failed to add interface %s to sandbox: %v", i.srcName, err)
|
||||
|
||||
Reference in New Issue
Block a user