Documentation
¶
Index ¶
- Constants
- func IsDHCPPlugin(driver string) bool
- type CapabilitiesResponse
- type CreateEndpointRequest
- type CreateEndpointResponse
- type CreateNetworkRequest
- type DHCPNetworkOptions
- type DeleteEndpointRequest
- type DeleteNetworkRequest
- type EndpointInterface
- type HealthResponse
- type IPAMData
- type InfoRequest
- type InfoResponse
- type InterfaceName
- type JoinRequest
- type JoinResponse
- type LeaveRequest
- type Options
- type Plugin
- func (p *Plugin) Close() error
- func (p *Plugin) CreateEndpoint(ctx context.Context, r CreateEndpointRequest) (CreateEndpointResponse, error)
- func (p *Plugin) CreateNetwork(r CreateNetworkRequest) error
- func (p *Plugin) DeleteEndpoint(ctx context.Context, r DeleteEndpointRequest) error
- func (p *Plugin) DeleteNetwork(r DeleteNetworkRequest) error
- func (p *Plugin) EndpointOperInfo(ctx context.Context, r InfoRequest) (InfoResponse, error)
- func (p *Plugin) Join(ctx context.Context, r JoinRequest) (JoinResponse, error)
- func (p *Plugin) Leave(ctx context.Context, r LeaveRequest) error
- func (p *Plugin) Listen(bindSock string) error
- func (p *Plugin) ListenMetrics(addr string) error
- type StaticRoute
Constants ¶
const ( RouteTypeNextHop = 0 RouteTypeOnLink = 1 )
libnetwork's route-type encoding for StaticRoute.RouteType. See https://github.com/moby/libnetwork/blob/master/docs/remote.md — 0 ("via gateway") expects a NextHop; 1 ("on-link / connected") has no next hop.
const ( ModeBridge = "bridge" ModeMacvlan = "macvlan" ModeIPvlan = "ipvlan" )
Network attachment modes selected by the `mode` driver option.
const CLIOptionsKey string = "com.docker.network.generic"
CLIOptionsKey is the key used in create network options by the CLI for custom options
const DriverName string = "net-dhcp"
DriverName is the name of the Docker Network Driver
Variables ¶
This section is empty.
Functions ¶
func IsDHCPPlugin ¶
IsDHCPPlugin checks if a Docker network driver is an instance of this plugin
Types ¶
type CapabilitiesResponse ¶
CapabilitiesResponse returns whether or not this network is global or local
type CreateEndpointRequest ¶
type CreateEndpointRequest struct {
NetworkID string
EndpointID string
Interface *EndpointInterface
Options map[string]interface{}
}
CreateEndpointRequest is sent by the daemon when an endpoint should be created
type CreateEndpointResponse ¶
type CreateEndpointResponse struct {
Interface *EndpointInterface
}
CreateEndpointResponse is sent as a response to a CreateEndpointRequest
type CreateNetworkRequest ¶
type CreateNetworkRequest struct {
NetworkID string
Options map[string]interface{}
IPv4Data []*IPAMData
IPv6Data []*IPAMData
}
CreateNetworkRequest is sent by the daemon when a network needs to be created
type DHCPNetworkOptions ¶
type DHCPNetworkOptions struct {
// Mode selects the attachment strategy: "bridge" (default, requires
// `bridge`) or "macvlan" (requires `parent`).
Mode string `mapstructure:"mode"`
Bridge string
Parent string `mapstructure:"parent"`
// Gateway, if set, overrides the default gateway returned by the
// upstream DHCP server. Useful for split-horizon LANs where
// containers should egress via a different router than the one
// the DHCP server advertises (e.g. VPN gateway).
Gateway string
IPv6 bool
LeaseTimeout time.Duration `mapstructure:"lease_timeout"`
IgnoreConflicts bool `mapstructure:"ignore_conflicts"`
SkipRoutes bool `mapstructure:"skip_routes"`
// PropagateDNS, when true, makes the plugin write DHCP option 6
// (v4 DNS server list) or option 23 (v6) into the container's
// /etc/resolv.conf on every bind/renew with a non-empty list.
// Default false to preserve historical behaviour where Docker's
// embedded resolver handled DNS — flipping this on means LAN-DNS
// names suddenly resolve from inside containers.
PropagateDNS bool `mapstructure:"propagate_dns"`
// PropagateMTU, when true, makes the plugin set the container link's
// MTU to DHCP option 26 on every bind/renew with a non-zero value.
// Default false because some networks advertise non-standard MTUs
// for reasons unrelated to host capability (e.g. hand-rolled tunnel
// fragments) and silently re-MTU'ing a container could surprise an
// operator. Opt-in keeps the behaviour change visible.
PropagateMTU bool `mapstructure:"propagate_mtu"`
// ClientID, when non-empty, overrides the derived DHCP option 61
// (Client Identifier) for every endpoint on this network. Bytes go
// on the wire prefixed with type byte 0x00 (RFC 2132 opaque).
//
// Default empty = derive per endpoint: from the MAC in bridge and
// macvlan (unique, and preserved across a restart, so the lease
// survives), from the Docker endpoint ID in ipvlan (whose slaves
// share the parent MAC). See resolveClientID.
//
// Operator caveat: a static ClientID across containers means the
// upstream DHCP server can't differentiate them — each new
// container will appear to be the same logical client and may
// receive the same lease. Typically only useful when paired with
// VendorClass to drive class-based policy that doesn't depend on
// per-client identity.
ClientID string `mapstructure:"client_id"`
// VendorClass, when non-empty, overrides the default DHCP option
// 60 (Vendor Class Identifier) value of "docker-net-dhcp" for
// every endpoint on this network. Lets DHCP servers using
// class-based policy (Cisco / Aruba / etc.) differentiate
// net-dhcp containers from other clients on the same LAN —
// for example to issue a different gateway or option set to
// containers tagged with a known vendor string.
VendorClass string `mapstructure:"vendor_class"`
// ValidateDHCP, when true, makes CreateNetwork run a one-shot
// DHCP probe on the parent NIC before the network is created,
// failing fast with a clear error if no DHCP server answers
// within the budget (see preflightProbeBudget). Catches
// misconfigurations (parent isolated from any DHCP server,
// firewall blocking UDP/67-68, broken VLAN tag) at create time
// rather than the first `docker run` attempt.
//
// macvlan / ipvlan modes only — bridge mode's "parent" is an
// existing Linux bridge, where the probe semantics are different
// and not yet implemented.
//
// The probe runs a full DHCPDISCOVER → REQUEST → ACK cycle
// (dhcpcd has no DISCOVER-only mode), so the upstream
// pool briefly sees one extra lease per `docker network create`
// with this opt-in. The probe MAC is random (locally-administered
// bit set) so it doesn't collide with anything stable upstream;
// the lease times out naturally rather than dragging CreateNetwork
// on a slow release path.
ValidateDHCP bool `mapstructure:"validate_dhcp"`
// RegisterDNS, when true, makes every endpoint on this network send
// the DHCP FQDN option (81 v4 / 39 v6, dhcpcd `fqdn both`) built from
// its resolved hostname, asking the DHCP server to register that name
// in DNS (forward + reverse). Default false: dynamic-DNS registration
// is a network-policy decision, never silent. Best-effort and advisory
// — many consumer routers ignore option 81, so this requests
// registration, it does not guarantee resolution. Reuses the same
// hostname already sent as the option-12 hint (#261).
RegisterDNS bool `mapstructure:"register_dns"`
// AuditLog, when true, appends every lease-lifecycle event on
// this network (bound / renew / release, plus release_failed when
// the DHCPRELEASE didn't complete) to STATE_DIR/leases.jsonl —
// an append-only JSONL audit trail answering "which IP did this
// container hold last Tuesday?" without dnsmasq-log archaeology
// (#109). Rotated at 16 MB or 30 days, whichever first; one
// rotated generation is kept. Default false: the ledger costs a
// disk write per lease event, and container-ID/IP correlation on
// disk is privacy-relevant in some environments — operators opt
// in deliberately. Append failures bump ledger_write_failures on
// /Plugin.Health and never affect lease handling.
AuditLog bool `mapstructure:"audit_log"`
// DHCPServers is an ordered preference list of DHCPv4 server
// addresses, e.g. "1.1.1.1,2.2.2.2": the first that answers within
// its slice of the acquisition budget wins, and the list is
// exhaustive — if none answers, acquisition fails rather than
// falling back to whichever server happened to reply. Naming your
// servers is what makes the list complete (#111).
//
// Empty (the default) accepts whichever OFFER arrives first, the
// historical behaviour.
DHCPServers string `mapstructure:"dhcp_servers"`
// DenyServers is an unordered list of DHCPv4 server addresses this
// network must never take a lease from, e.g. "3.3.3.3" — a rogue
// appliance or a second router on the segment (#669).
//
// This is a permission, not a preference: it composes with
// DHCPServers rather than competing with it. See serverPolicy for
// why the two cannot both be handed to dhcpcd as directives.
DenyServers string `mapstructure:"dhcp_deny_servers"`
}
DHCPNetworkOptions contains options for the DHCP network driver
type DeleteEndpointRequest ¶
DeleteEndpointRequest is sent by the daemon when an endpoint needs to be removed
type DeleteNetworkRequest ¶
type DeleteNetworkRequest struct {
NetworkID string
}
DeleteNetworkRequest is sent by the daemon when a network needs to be removed
type EndpointInterface ¶
EndpointInterface contains endpoint interface information
type HealthResponse ¶
type HealthResponse struct {
Healthy bool `json:"healthy"`
// InstanceID identifies the plugin process that served this
// response. Every counter below is in-memory and returns to zero
// when the process does, so two reads are only comparable as a
// delta when their InstanceID matches (#405).
//
// uptime_seconds is a weaker version of the same signal: it does
// reset, but a plugin that restarts early in a long window and then
// runs longer than the first reading shows uptime going *up* across
// the pair, and the reset goes unnoticed. Comparing ids has no such
// blind spot.
InstanceID string `json:"instance_id"`
UptimeSeconds float64 `json:"uptime_seconds"`
ActiveEndpoints int `json:"active_endpoints"`
PendingHints int `json:"pending_hints"`
RecoveredOK int32 `json:"recovered_ok"`
// RecoveryFailed counts post-restart recoveries that failed for a
// container that was still running: it has no renewal client and
// will lose its lease at expiry. Healthy-affecting.
//
// Two conditions were folded into this counter historically and are
// now split out, because neither leaves a running container without
// a renewal client and both are routine after a daemon restart:
// RecoveryDeferred (#383) and RecoveryAbortedContainerGone (#376).
RecoveryFailed int32 `json:"recovery_failed"`
// RecoveryDeferred counts the times recovery met a daemon that was
// not serving yet and was retried once the socket came up (#383).
// Docker respawns the plugin during its own startup, so this is the
// expected state at that moment, not a fault — NOT Healthy-affecting.
// A rise paired with recovery_failed means the retry ran out too:
// that pair is the signal that endpoints really are unrecovered.
RecoveryDeferred int32 `json:"recovery_deferred"`
// RecoveryAbortedContainerGone counts recoveries abandoned because
// the container had already exited or been removed (#376). Not
// Healthy-affecting: nothing is running without a renewal client.
// The recovery-side twin of JoinAbortedContainerGone, and normal
// after a daemon restart that outlived some containers.
RecoveryAbortedContainerGone int32 `json:"recovery_aborted_container_gone"`
// RecoveryNetworkGone counts networks skipped during post-restart
// recovery because they had been removed between the NetworkList
// that found them and the NetworkInspect that reads their detail
// (#648). Not Healthy-affecting: a network that is gone leaves no
// running container without a renewal client. Counted rather than
// silent so a host churning networks under a restarting daemon is
// still visible. It landed in recovery_failed until #648, where it
// was fatal.
RecoveryNetworkGone int32 `json:"recovery_network_gone"`
// RecoveryFingerprintsSkipped counts endpoints recovery adopted but
// could not describe: the ContainerInspect that would have supplied
// the hostname did not answer, or answered with no hostname (#721).
// Not Healthy-affecting: the endpoint has a renewal client, so no
// running container is without one — what it has lost is the
// tombstone that would have carried its MAC and address across its
// next `docker restart`.
//
// It exists because #721's fix would otherwise have inherited the
// invisibility of the bug it closes. A skipped fingerprint means no
// tombstone, and the only outward sign of that was
// tombstones_consumed staying flat — indistinguishable from a quiet
// host. A hostname REFUSED by safeHostname is not counted here; it
// moves unsafe_hostnames_rejected instead, so "the daemon would not
// answer me" stays distinguishable from "a container sent a hostname
// nobody should send".
RecoveryFingerprintsSkipped int32 `json:"recovery_fingerprints_skipped"`
// RecoveryAlreadyManaged counts endpoints a recovery walk found
// already registered to another manager and therefore left alone —
// a Join reached them first. Not Healthy-affecting: the endpoint has
// a renewal client, it just is not the one this walk would have
// built. Counted because it is the only outward evidence of recovery
// racing a Join, and because the completion log used to report those
// endpoints as recovered (#480).
RecoveryAlreadyManaged int32 `json:"recovery_already_managed"`
// JoinStartFailures counts persistent-client Start failures at
// Join time (#317): a running container with no renewal client.
// Healthy-affecting — same operator action as recovery_failed
// (find the cause in the plugin log, restart the container).
JoinStartFailures int32 `json:"join_start_failures"`
// JoinAbortedContainerGone counts attaches abandoned because the
// container exited before the persistent client was up (#373). Not
// Healthy-affecting: there is no running container without a
// renewal client. Worth watching anyway — a rise means containers
// are dying seconds after start.
JoinAbortedContainerGone int32 `json:"join_aborted_container_gone"`
// JoinAbortedNoContainer counts attaches abandoned because no
// container ever claimed the endpoint on the network, and whose
// address was therefore released rather than left to expire (#566).
// Not Healthy-affecting: nothing is running without a renewal
// client, because nothing is running. A rise means endpoints are
// being created for containers that never attach.
JoinAbortedNoContainer int32 `json:"join_aborted_no_container"`
// JoinAttachSlow counts attaches that succeeded only after
// outlasting AwaitTimeout, waiting on a daemon that was busy with
// the container being attached. Not healthy-affecting — these are
// successes — but a rising count is the visible form of #406.
JoinAttachSlow int32 `json:"join_attach_slow"`
// RestartLinkUpWaited counts child links brought up only after
// waiting out the departing link's hold on the address (#408). Not
// healthy-affecting: this is the fix working, and it is counted so
// the window is visible rather than inferred — the same reason
// JoinAttachSlow exists.
RestartLinkUpWaited int32 `json:"restart_link_up_waited"`
// RestartLinkUpTimeouts counts that wait outlasting its budget. The
// restart then fails with `address already in use`. Not
// healthy-affecting despite being a real failure: it surfaces
// through CreateEndpoint to the operator directly, and `healthy`
// is for faults nothing else reports (#422).
RestartLinkUpTimeouts int32 `json:"restart_link_up_timeouts"`
// JoinAbortedEndpointLeft counts attaches cancelled because the
// endpoint left while the attach was still running. Not
// healthy-affecting: there is no running container missing a
// renewal client.
JoinAbortedEndpointLeft int32 `json:"join_aborted_endpoint_left"`
// TombstoneWriteFailures counts tombstone persistence failures.
// Healthy-affecting: an endpoint will not keep its address across a
// restart.
//
// It moves on a failed READ as well as a failed write. Since #724,
// a transient read error (EIO, EMFILE, a read racing a writer) makes
// the write path refuse rather than rewrite the file from nothing,
// and that refusal is counted here — the consequence is identical to
// a failed write, and the name being narrower than the meaning is
// worth one sentence rather than a fourth counter.
TombstoneWriteFailures int32 `json:"tombstone_write_failures"`
// TombstoneQuarantines counts times the tombstone file was found
// unparseable and moved aside as tombstones.json.corrupt-<ts>
// (#724). Healthy-affecting, and the counter that costs the most
// when it moves: a write failure loses ONE container's MAC and
// address, a quarantine loses every live tombstone on the host, so
// every container restarting for the rest of the TTL window comes
// back with a new identity.
//
// Separate from TombstoneWriteFailures on purpose. The two have
// different remedies — a write failure means the disk is full or
// read-only, a quarantine leaves a file to read — and merging them
// would leave an operator unable to tell which one they are being
// paged for.
//
// WHY IT LATCHES `healthy`, WHICH IS NOT OBVIOUS. The argument
// against is real: the condition is self-healing by construction —
// the file is renamed away, the plugin continues correctly from an
// empty set, and the cost is bounded at one TTL window of address
// instability for containers that happen to restart in it. Against
// that, the remedy for a latched `healthy` is to restart the
// plugin, which tears down every managed endpoint's renewal client:
// strictly more damaging than the fault. On those terms alone it
// would not latch.
//
// It latches anyway, for two reasons. Consistency first:
// TombstoneWriteFailures is already healthy-affecting, and a
// quarantine is the same family — tombstones did not work. Splitting
// them would mean an I/O error latches and actual file corruption
// does not. And the one that decides it: a quarantine does not mean
// tombstones had a bad minute, it means SOMETHING WROTE GARBAGE
// into stateDir — a host bind mount that survives `docker plugin rm`
// and upgrade, and that now also holds the versioned options file.
// The self-healing is about the tombstones. The signal is about the
// disk, and that is worth an operator's attention even though this
// particular symptom cleared itself.
TombstoneQuarantines int32 `json:"tombstone_quarantines"`
// UnsafeHostnamesRejected counts container hostnames dropped before
// reaching the generated DHCP client config because they carried a
// control character (#692). NOT healthy-affecting: the drop is the
// safe outcome and the lease proceeds. It is reported because a
// legitimate hostname never contains one, so a rising value is
// somebody probing rather than background noise.
UnsafeHostnamesRejected int32 `json:"unsafe_hostnames_rejected"`
// UnsafeOptionValuesDropped counts server-chosen DHCP string
// values refused before use because they carried a control
// character, plus option-15 domains truncated at their first space.
// NOT healthy-affecting: dropping is the safe outcome and the lease
// proceeds. Its sibling above covers the value the CONTAINER
// chooses; this one covers the values the SERVER chooses, which is
// the larger set and the one nothing filtered before (#703, #704).
UnsafeOptionValuesDropped int32 `json:"unsafe_option_values_dropped"`
// NetworkOptionsRejected counts endpoint operations that met a
// network's stored options and would not act on them as written:
// an interface name the kernel would not accept, or a mode this
// plugin does not implement (#727). DeleteEndpoint counts without
// refusing, so a rise does not mean nothing was torn down. NOT
// healthy-affecting: refusing is the safe outcome and the
// operation already fails visibly to Docker; one network's record
// is broken, not the plugin. A non-zero value means options
// written before name validation existed (#705), or a hand-edited
// state directory.
NetworkOptionsRejected int32 `json:"network_options_rejected"`
// DNSPropagationPIDMismatches counts DNS propagations refused
// because the container PID resolved through Docker no longer
// belonged to that container by the time the plugin acted on it
// (#688). NOT healthy-affecting: refusing is the safe outcome and
// the container keeps the resolv.conf it had. It is reported
// because the plugin shares the host PID namespace, so each one is
// a write that would otherwise have gone to an unrelated host
// process.
DNSPropagationPIDMismatches int32 `json:"dns_propagation_pid_mismatches"`
// NetnsPIDMismatches counts sandbox network-namespace opens refused
// because the container PID resolved through Docker no longer named
// that container. The attach fails, so this is not silent -- but the
// failure looks like a slow start; only this counter distinguishes a
// recycled PID from one.
NetnsPIDMismatches int32 `json:"netns_pid_mismatches"`
// DHCPRoutesApplied counts DHCP option-121 classless static routes
// handed to Docker. DHCPDefaultRouteSuperseded counts the Joins
// where those routes cover 0.0.0.0/0 by union rather than by a
// literal default entry -- i.e. the container's egress goes to the
// option-121 next hop even though the reported gateway, and
// `docker inspect`, still name the router from option 3. Neither is
// healthy-affecting: this is legitimate split-tunnel behaviour as
// often as it is not. They are the evidence trail (#700).
DHCPRoutesApplied int32 `json:"dhcp_routes_applied"`
DHCPDefaultRouteSuperseded int32 `json:"dhcp_default_route_superseded"`
// LeaseTimeClamped counts option-51 lifetimes cut down before use
// as the outage watchdog's deadline. NOT healthy-affecting -- the
// clamp is the safe outcome and the lease time reported to
// operators is unchanged. Any non-zero value is worth reading: an
// over-long lease is how a server switches this plugin's only
// silent-lapse detector off (#701).
LeaseTimeClamped int32 `json:"lease_time_clamped"`
// MTURefused counts option-26 MTUs outside the range the plugin
// will apply; the link keeps the MTU it had. NOT healthy-affecting.
// Read it because the alternative was silent: a link clamped near
// the RFC floor black-holes path MTU discovery and looks like a
// slow network, not a misconfiguration (#702).
MTURefused int32 `json:"mtu_refused"`
// TombstonesConsumed counts CreateEndpoints that replayed a fresh
// tombstone and so handed a recreated container its previous
// MAC/IP. Not Healthy-affecting: this is the address-stability
// mechanism working.
//
// It is the counterpart to RecoveredOK. Between them they say which
// of the two paths preserved an address across a restart, which is
// what makes "the address survived, but via neither path" a
// detectable state rather than a silent pass (#386).
TombstonesConsumed int32 `json:"tombstones_consumed"`
// LeaseChanged counts renewals where dhcpcd returned a different
// IP than the manager last recorded. Not Healthy-affecting (it
// doesn't break Docker's view fatally — see plugin.go for the
// truthfulness-gap discussion), but worth alerting on for
// long-running containers.
LeaseChanged int32 `json:"lease_changed"`
// AddressConflicts counts leases whose address was already held by
// another device on the segment, found by probing after the lease
// (#524). Healthy-affecting: the endpoint is up and reporting an
// address that does not work, and no other counter moves for it.
//
// ConflictProbeFailures counts probes that could not run. NOT
// Healthy-affecting — it says the question went unasked, not that
// the answer was bad. Watch it anyway: a detector that has stopped
// running looks identical to a clean segment.
AddressConflicts int32 `json:"address_conflicts"`
ConflictProbeFailures int32 `json:"conflict_probe_failures"`
// ConflictProbeStaleRoutes counts leftover probe routes reclaimed
// from a probe that was cut short before it could clean up (#572).
// Not Healthy-affecting — the probe that reclaimed it went on to
// run — but a rising count means the plugin is being stopped inside
// probe windows.
ConflictProbeStaleRoutes int32 `json:"conflict_probe_stale_routes"`
// ConflictProbeStaleAddrs counts leftover borrowed probe SOURCE
// addresses reclaimed from the parent NIC (#723). Its sibling
// above covers the leftover route; this one covers the address the
// route was sourced from, which nothing recognised because it is
// randomly chosen. NOT healthy-affecting: the probe went on to run.
ConflictProbeStaleAddrs int32 `json:"conflict_probe_stale_addrs"`
// AddressConflictProbes counts probes that reached a verdict. Read
// it before believing address_conflicts=0: a zero here means the
// detector did not run, not that the segment is clean.
AddressConflictProbes int32 `json:"address_conflict_probes"`
// SandboxNetnsVisible is how many sandbox netns entries the plugin
// can currently see, or -1 when it cannot read the directory at all
// (#567). Sampled at request time rather than accumulated — it
// describes the plugin's view of the host right now, not something
// that happened.
//
// It exists because the evidence sandboxGone depends on was
// unreachable for the entire life of this plugin and nothing said
// so. The directory is not part of the image; it is bind-mounted by
// config.json, and before #567 it was not mounted at all, so
// os.ReadDir failed on every call and sandboxGone answered "no
// usable evidence" forever. A dead branch is invisible precisely
// because it never does anything.
//
// READ IT AGAINST ACTIVE_ENDPOINTS, NOT ON ITS OWN. The two
// failure modes are opposite and only the comparison separates
// them:
//
// -1 the directory is unreadable — the mount is missing. Every
// sandboxGone answer is "no evidence", which is safe but
// useless: the API 404 becomes the only source of truth.
// 0 with endpoints attached, the directory is readable but
// WRONG — mounted from somewhere with no sandboxes in it.
// This is the dangerous one. sandboxGone finds no entry
// matching any key and concludes every container has
// vanished, which is worse than never answering.
//
// A plain zero with no endpoints attached is neither: there is
// genuinely nothing to see.
SandboxNetnsVisible int32 `json:"sandbox_netns_visible"`
// DHCP-wire counters (T2-4). Naming intentionally drops the
// Prometheus `_total` suffix to stay consistent with the
// existing fields above; the issue's proposal listed them with
// `_total` for documentation clarity but the wire field is the
// shorter form.
//
// Each of these is the SUM of its *_v4 and *_v6 halves below, added
// in healthSnapshot (#730). It is not a counter in its own right,
// and nothing increments it. The meaning operators alert on is
// unchanged — it was a v4+v6 total before and it is a v4+v6 total
// now — but it is now derived from the halves rather than the
// halves being derived from it.
LeasesObtained int32 `json:"leases_obtained"`
LeasesRenewed int32 `json:"leases_renewed"`
// DHCPServerTierFallbacks counts STEPS DOWN the dhcp_servers
// ladder: one per preferred entry that did not answer inside its
// slice of the budget and handed on to the next (#111). One
// acquisition against three silent preferred servers adds 2, not
// 1 — the counter measures how far down the list acquisition had
// to walk, which is the number worth having and is what the code
// has always produced. Three of the four places this was described
// said "acquisitions" instead, and #731 is that drift.
//
// Not healthy-affecting — the endpoint still got an address; a
// steady rise is how a silently-dead primary shows up.
DHCPServerTierFallbacks int32 `json:"dhcp_server_tier_fallbacks"`
// DHCPServerPolicyExhausted counts acquisitions abandoned because no
// server listed in dhcp_servers answered (#111). Not Healthy-
// affecting on its own: the acquisition failure it accompanies is
// already counted and already fails the operation.
DHCPServerPolicyExhausted int32 `json:"dhcp_server_policy_exhausted"`
// DHCPServerPolicyTimeouts counts dhcp_timeouts on endpoints whose
// renewal client is restricted to dhcp_servers (#731). A strict
// subset of DHCPTimeouts and NOT Healthy-affecting: every tick it
// counts is already counted there, and weighting one outage twice
// would make a policy-restricted endpoint look worse than an
// unrestricted one failing identically.
DHCPServerPolicyTimeouts int32 `json:"dhcp_server_policy_timeouts"`
DHCPTimeouts int32 `json:"dhcp_timeouts"`
LeaseReleaseFailures int32 `json:"lease_release_failures"`
// NAKsReceived counts server NAKs on renewal/rebind. Not
// Healthy-affecting on its own — dhcpcd recovers by
// re-DISCOVERing — but each NAK-triggered re-bind widens the
// docker-inspect divergence tracked by lease_changed (#128).
NAKsReceived int32 `json:"naks_received"`
// DisplacedStops counts managers displaced at Join — a Join that
// found a recovery-registered manager still in the registry for
// the same endpoint (plugin restart racing a container restart).
// Not Healthy-affecting: the displaced client is stopped and
// released, and the new one takes over. A climbing value means
// containers are restarting into a plugin that had recovered them,
// so pair it with recovered_ok when diagnosing a restart loop.
DisplacedStops int32 `json:"displaced_stops"`
// OrphanedLeasesReleased / OrphanedLeaseReleaseFailures cover the
// lease acquired by the CreateEndpoint one-shot when no persistent
// client ever took ownership of it, because the container exited
// before Join's async Start could attach (#370). The plugin
// synthesises a release rather than leaving the address held until
// its own expiry.
//
// Neither is Healthy-affecting. A short-lived container is an
// ordinary lifecycle, and a failed synthesised release costs one
// lease until it expires — alert on the failure rate, not on a
// latched unhealthy. Read the two together: releases climbing with
// failures flat is the mechanism working.
OrphanedLeasesReleased int32 `json:"orphaned_leases_released"`
OrphanedLeaseReleaseFailures int32 `json:"orphaned_lease_release_failures"`
// ParentLinkWaits / ParentLinkWaitTimeouts cover contention on a
// shared parent NIC. A parent is a macvlan port or an ipvlan port,
// never both, so an orphan-lease reclaim holding one asynchronously
// can collide with an endpoint asking for the other (#486/#549).
// The plugin queues them per parent instead.
//
// Waits counts the operations that had to queue; timeouts counts
// those that gave up after parentGateBudget and went to the kernel
// anyway. Neither is Healthy-affecting: queuing is the mechanism
// working, and a timeout only restores the behaviour that existed
// before the queue did. Timeouts climbing is the actionable one —
// it means a reclaim is holding a parent far longer than its DORA
// should take, and container starts on that NIC are failing with
// "device or resource busy".
ParentLinkWaits int32 `json:"parent_link_waits"`
ParentLinkWaitTimeouts int32 `json:"parent_link_wait_timeouts"`
// LedgerWriteFailures counts failed appends to the audit_log
// lease ledger (#109). Not Healthy-affecting — a lost audit line
// degrades forensics, not networking; operators using audit_log
// alert on this directly.
LedgerWriteFailures int32 `json:"ledger_write_failures"`
// Per-family breakdown of the wire counters (#212, #730). Both
// halves are STORED; the un-suffixed field above is their sum,
// computed in healthSnapshot from the same two values rendered
// here. It is not a third counter, and neither half is a subset of
// it. On a dual-stack host this isolates the v6-specific failure
// signal (NAK/timeout) the aggregate hides.
//
// Until #730 the v4 share was not stored at all: the un-suffixed
// field was the counter and the v4 number was recovered by
// subtracting *_v6 from it at render time. Two independently
// updated atomics combined by subtraction can produce a value lower
// than the previous read, and a counter that decreases is a reset
// to Prometheus. Storing both and adding for the total is
// monotonic under every interleaving; subtracting is not.
LeaseChangedV4 int32 `json:"lease_changed_v4"`
LeasesObtainedV4 int32 `json:"leases_obtained_v4"`
LeasesRenewedV4 int32 `json:"leases_renewed_v4"`
DHCPTimeoutsV4 int32 `json:"dhcp_timeouts_v4"`
NAKsReceivedV4 int32 `json:"naks_received_v4"`
// LeaseReleaseFailuresV4 is the v4 half of LeaseReleaseFailures.
LeaseReleaseFailuresV4 int32 `json:"lease_release_failures_v4"`
LeaseChangedV6 int32 `json:"lease_changed_v6"`
LeasesObtainedV6 int32 `json:"leases_obtained_v6"`
LeasesRenewedV6 int32 `json:"leases_renewed_v6"`
DHCPTimeoutsV6 int32 `json:"dhcp_timeouts_v6"`
NAKsReceivedV6 int32 `json:"naks_received_v6"`
// LeaseReleaseFailuresV6 is the v6 share of LeaseReleaseFailures
// (#608): the persistent DHCPv6 client held a binding and its
// SIGTERM-driven RELEASE did not complete cleanly.
LeaseReleaseFailuresV6 int32 `json:"lease_release_failures_v6"`
}
HealthResponse is the payload returned by /Plugin.Health.
WHAT `Healthy` MEANS ¶
False when any of FIVE counters is non-zero: recovery_failed, join_start_failures, tombstone_write_failures, address_conflicts and tombstone_quarantines. Each is marked Healthy-affecting on its field below, and docs/reference.md states the same set in four more places; scripts/check-health-contract.sh keeps those in step.
This comment said "at least one plugin-restart recovery failed" — ONE counter — from before v1.6.0 until #724. The expression 350 lines below had four by then. It is the comment a developer reads first when adding a counter, which is exactly how it stayed wrong for two releases: the gate reads reference.md and the expression, not this. Corrected here rather than only in the docs, because the next person to add a Healthy-affecting counter reads this file (#638, #724).
IT LATCHES, AND THE OBVIOUS REMEDY DOES NOT CLEAR IT ¶
Every counter behind the flag is a monotonic atomic; nothing decrements them. So `healthy: false` means "a fault occurred at some point during THIS plugin process", not "something is wrong right now". An operator who restarts the affected containers fixes the condition — and the flag stays false. The only thing that clears it is restarting the plugin, which tears down the renewal client of every managed endpoint on the host, so it is not a free action and must not be taken as routine hygiene. Pair a reading with InstanceID to tell "still the same process, still latched" from "a new process that has already gone bad".
That is deliberate. An alert that goes quiet on its own is worse than one that never clears, because the operator learns nothing from the silence. If "unhealthy right now" is ever wanted, it is a new field, not a change to this one.
type IPAMData ¶
type IPAMData struct {
AddressSpace string
Pool string
Gateway string
AuxAddresses map[string]interface{}
}
IPAMData contains IPv4 or IPv6 addressing information
type InfoRequest ¶
InfoRequest is sent by the daemon when querying endpoint information
type InfoResponse ¶
InfoResponse is endpoint information sent in response to an InfoRequest
type InterfaceName ¶
InterfaceName consists of the name of the interface in the global netns and the desired prefix to be appended to the interface inside the container netns.
DstName, when non-empty, asks libnetwork for that exact name inside the container instead of DstPrefix+index. The remote-driver API has carried the field for years, but as of moby master the remote proxy drops it (drivers/remote/driver.go calls `iface.SetNames(SrcName, DstPrefix, "")`), so engines do not yet apply it for plugin drivers — built-in drivers got per-driver interface_name support in engine 28, remote drivers were left out. We return it anyway: it is the documented response shape, costs nothing on engines that ignore it, and activates the moment the upstream pass-through lands (#125).
type JoinRequest ¶
type JoinRequest struct {
NetworkID string
EndpointID string
SandboxKey string
Options map[string]interface{}
}
JoinRequest is sent by the Daemon when an endpoint needs be joined to a network
type JoinResponse ¶
type JoinResponse struct {
InterfaceName InterfaceName
Gateway string
GatewayIPv6 string
StaticRoutes []*StaticRoute
DisableGatewayService bool
}
JoinResponse is sent in response to a JoinRequest
type LeaveRequest ¶
LeaveRequest is sent by the daemon when a endpoint is leaving a network
type Options ¶
type Options struct {
// AwaitTimeout caps the polling helpers (sandbox readiness, link
// rename, netns appearance). AWAIT_TIMEOUT, default 10s.
AwaitTimeout time.Duration
// OutageTick is how often the DHCP-outage watchdog re-checks, and
// so the resolution of dhcp_timeouts. OUTAGE_TICK, default 30s.
OutageTick time.Duration
// OutageGrace is the settling time before the watchdog will call an
// outage. It must stay comfortably above how long a healthy client
// takes to acquire its first lease — below that, ordinary start-up
// registers as an outage. OUTAGE_GRACE, default 25s.
OutageGrace time.Duration
// RequestCaptureDir, when non-empty, tees every libnetwork request
// body into that directory so an integration run can be turned into
// the replay fixtures under pkg/plugin/testdata/requests (#644).
// REQUEST_CAPTURE_DIR, default empty (disabled).
//
// Test instrumentation: it is declared in config-cover.json only,
// alongside GOCOVERDIR, and empty here costs the shipped plugin
// nothing — captureHandler returns the mux unwrapped.
RequestCaptureDir string
}
Options carries the plugin's runtime knobs. Every field is sourced from an environment variable declared in config.json and parsed in cmd/net-dhcp; a zero field means "unset", and NewPlugin substitutes the documented default. Grouping them beats growing NewPlugin's parameter list one knob at a time.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the DHCP network plugin
func NewPlugin ¶
NewPlugin creates a new Plugin. Zero-valued Options fields take the documented defaults, so NewPlugin(Options{}) is a valid production configuration.
func (*Plugin) CreateEndpoint ¶
func (p *Plugin) CreateEndpoint(ctx context.Context, r CreateEndpointRequest) (CreateEndpointResponse, error)
CreateEndpoint creates the per-endpoint host-side network plumbing (veth pair in bridge mode, macvlan child in macvlan mode), runs dhcpcd once to acquire an initial lease, and stashes the result for Join. Docker moves the link into the container's netns when it acts on our Join response.
func (*Plugin) CreateNetwork ¶
func (p *Plugin) CreateNetwork(r CreateNetworkRequest) error
CreateNetwork validates network creation: option shape (pure), then existence of the parent interface (bridge or NIC depending on mode), the null IPAM driver requirement, and — for bridge mode — that no other Docker network already owns this bridge's address space.
func (*Plugin) DeleteEndpoint ¶
func (p *Plugin) DeleteEndpoint(ctx context.Context, r DeleteEndpointRequest) error
DeleteEndpoint deletes the host-side network plumbing for an endpoint. In bridge mode that's the veth pair (deleting one side removes the peer). In macvlan mode the link has typically already been moved into the container netns and reaped with it, so cleanup is best-effort.
func (*Plugin) DeleteNetwork ¶
func (p *Plugin) DeleteNetwork(r DeleteNetworkRequest) error
DeleteNetwork "deletes" a DHCP network (the bridge is managed by the user). We also evict any persistent DHCP managers attached to this network: libnetwork doesn't issue Leave for endpoints in stopped containers when the network is removed, so without this prune they linger as ghost entries in /Plugin.Health.active_endpoints. Stop is safe to call against a manager whose underlying netns is gone — it just unblocks the dhcpcd-events loop and returns; dhcpcd itself may have already self-exited because its netns vanished.
func (*Plugin) EndpointOperInfo ¶
func (p *Plugin) EndpointOperInfo(ctx context.Context, r InfoRequest) (InfoResponse, error)
EndpointOperInfo retrieves some info about an existing endpoint
func (*Plugin) Join ¶
func (p *Plugin) Join(ctx context.Context, r JoinRequest) (JoinResponse, error)
func (*Plugin) Leave ¶
func (p *Plugin) Leave(ctx context.Context, r LeaveRequest) error
Leave stops the persistent DHCP client for an endpoint
func (*Plugin) ListenMetrics ¶ added in v1.8.0
Close stops the plugin. The HTTP server is shut down FIRST so no new Join can register a manager while (or after) we stop the existing ones — with the old ordering a Join dispatched during the stop fan-out installed a manager into the fresh registry that nobody ever stopped, orphaning its lease (no DHCPRELEASE) and its dhcpcd. Persistent DHCP clients are then stopped before process exit so they get a chance to send DHCPRELEASE for their leases — otherwise plugin upgrade or `docker plugin disable` would orphan every active lease at the upstream DHCP server, defeating the release-on-stop contract Leave normally honors. ListenMetrics starts the optional TCP listener for /metrics.
Off unless METRICS_ADDR is set, and that default is deliberate. The plugin holds CAP_NET_ADMIN, CAP_SYS_ADMIN and CAP_SYS_PTRACE with "network": {"type": "host"} in config.json, so any port it opens is on the host's own network namespace. Opening one has to be a decision an operator made, not something they inherited by upgrading (#651).
The mux here carries /metrics ALONE. See the metricsServer field for why that is load-bearing rather than tidy.
Returns once the listener is bound, so a bad METRICS_ADDR fails at startup where an operator will see it, rather than in a goroutine that logs and leaves the plugin running without the endpoint they asked for.
type StaticRoute ¶
StaticRoute contains static route information