242 lines
9.4 KiB
Go
242 lines
9.4 KiB
Go
package netconfig
|
|
|
|
import (
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Defaults for the tunable settings. They can be overridden with the
|
|
// corresponding With* Option when constructing a Configurator.
|
|
const (
|
|
// defaultConnectivityTimeout bounds the HTTP reachability test run after an
|
|
// address or gateway change so it cannot stall the rollback logic.
|
|
defaultConnectivityTimeout = 10 * time.Second
|
|
// defaultPingCount is the number of ICMP probes used to confirm a gateway or
|
|
// candidate address is reachable.
|
|
defaultPingCount = 5
|
|
// defaultPingTimeout bounds a single ping run.
|
|
defaultPingTimeout = 20 * time.Second
|
|
// defaultBackupRetention is how many .bak.* copies are kept per original
|
|
// configuration file. Older backups beyond this number are pruned on save.
|
|
defaultBackupRetention = 5
|
|
// defaultServiceReadyTimeout bounds how long NewConfigurator waits for a
|
|
// backend's daemon to come up and finish starting. It only elapses in full
|
|
// on a host where the service is registered to run but never arrives.
|
|
defaultServiceReadyTimeout = 60 * time.Second
|
|
)
|
|
|
|
// Logger is the minimal logging surface used across the package. It is
|
|
// satisfied by the standard library *log.Logger, logrus, and most other
|
|
// logging libraries, so callers can plug in their own logger via SetLogger.
|
|
type Logger interface {
|
|
Printf(format string, args ...interface{})
|
|
Println(args ...interface{})
|
|
}
|
|
|
|
// logger is the package-wide logger. It defaults to the logrus standard
|
|
// logger and can be replaced with SetLogger. It is package scoped because the
|
|
// configuration backends and control panels log independently of any single
|
|
// Configurator instance.
|
|
var logger Logger = log.StandardLogger()
|
|
|
|
// SetLogger replaces the package-wide logger used for non-fatal diagnostics.
|
|
// It is package scoped (not a per-Configurator option) because the configuration
|
|
// backends and control panels log independently of any single Configurator
|
|
// instance; the setting therefore applies to every Configurator in the process.
|
|
// A nil logger is ignored.
|
|
func SetLogger(l Logger) {
|
|
if l != nil {
|
|
logger = l
|
|
}
|
|
}
|
|
|
|
// configOptions holds the tunable settings applied when constructing a
|
|
// Configurator. Use the With* Option helpers to set them.
|
|
type configOptions struct {
|
|
// testAddress is the URL fetched to confirm connectivity after an
|
|
// address or gateway change.
|
|
testAddress string
|
|
// skipConnectivityCheck disables the post-change internet reachability
|
|
// test and its automatic rollback.
|
|
skipConnectivityCheck bool
|
|
// connectivityTimeout bounds the HTTP reachability test.
|
|
connectivityTimeout time.Duration
|
|
// pingCount is the number of ICMP probes per ping test.
|
|
pingCount int
|
|
// pingTimeout bounds a single ping run.
|
|
pingTimeout time.Duration
|
|
// backupRetention is the number of .bak.* copies kept per original
|
|
// configuration file; <= 0 disables pruning (keeps all backups).
|
|
backupRetention int
|
|
// allowPrimaryRemoval lets RemoveAddress remove an address that is the
|
|
// primary of its family, which is otherwise refused.
|
|
allowPrimaryRemoval bool
|
|
// skipPanels makes RemoveAddress bypass all control-panel backends
|
|
// (cPanel/Plesk/InterWorx) and only remove the address from the running
|
|
// system and the network-manager configuration files.
|
|
skipPanels bool
|
|
// allowNoBackends lets NewConfigurator succeed on a Linux host where no
|
|
// network configuration backend was detected, where changes would otherwise
|
|
// apply to the running system without persisting.
|
|
allowNoBackends bool
|
|
// serviceReadyTimeout bounds how long backend detection waits for a
|
|
// daemon-backed backend (NetworkManager) to become ready; <= 0 does not
|
|
// wait at all.
|
|
serviceReadyTimeout time.Duration
|
|
}
|
|
|
|
// defaultConfigOptions returns the options used when no Option is supplied.
|
|
func defaultConfigOptions() *configOptions {
|
|
return &configOptions{
|
|
testAddress: defaultInternetTestAddress,
|
|
connectivityTimeout: defaultConnectivityTimeout,
|
|
pingCount: defaultPingCount,
|
|
pingTimeout: defaultPingTimeout,
|
|
backupRetention: defaultBackupRetention,
|
|
serviceReadyTimeout: defaultServiceReadyTimeout,
|
|
}
|
|
}
|
|
|
|
// newConfigOptions builds a configOptions from the supplied options, starting
|
|
// from the package defaults.
|
|
func newConfigOptions(opts ...Option) *configOptions {
|
|
o := defaultConfigOptions()
|
|
for _, opt := range opts {
|
|
if opt != nil {
|
|
opt(o)
|
|
}
|
|
}
|
|
return o
|
|
}
|
|
|
|
// Option configures a Configurator constructed with NewConfigurator.
|
|
type Option func(*configOptions)
|
|
|
|
// WithTestAddress overrides the URL used for the post-change connectivity
|
|
// test. The two sentinel values "test_success" and "test_fail" force the test
|
|
// to pass or fail without performing a request, which is useful in tests.
|
|
func WithTestAddress(addr string) Option {
|
|
return func(o *configOptions) {
|
|
if addr != "" {
|
|
o.testAddress = addr
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithConnectivityCheck enables or disables the post-change internet
|
|
// reachability test (and the automatic rollback that depends on it). It is
|
|
// enabled by default; pass false to skip it, for example on hosts with no
|
|
// outbound internet access.
|
|
func WithConnectivityCheck(enabled bool) Option {
|
|
return func(o *configOptions) {
|
|
o.skipConnectivityCheck = !enabled
|
|
}
|
|
}
|
|
|
|
// WithSkipConnectivityCheck is a convenience option that disables the post-change
|
|
// internet reachability test and its automatic rollback. It is equivalent to
|
|
// WithConnectivityCheck(false).
|
|
func WithSkipConnectivityCheck() Option {
|
|
return WithConnectivityCheck(false)
|
|
}
|
|
|
|
// WithConnectivityTimeout sets the timeout for the HTTP reachability test run
|
|
// after an address or gateway change. A smaller value fails faster on a
|
|
// black-holed route; a larger value tolerates slower links.
|
|
func WithConnectivityTimeout(d time.Duration) Option {
|
|
return func(o *configOptions) {
|
|
if d > 0 {
|
|
o.connectivityTimeout = d
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithPingCount sets the number of ICMP probes sent when confirming that a
|
|
// candidate address or gateway is reachable. More probes are more reliable on
|
|
// lossy links but take longer.
|
|
func WithPingCount(n int) Option {
|
|
return func(o *configOptions) {
|
|
if n > 0 {
|
|
o.pingCount = n
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithPingTimeout sets the overall timeout for a single ping run.
|
|
func WithPingTimeout(d time.Duration) Option {
|
|
return func(o *configOptions) {
|
|
if d > 0 {
|
|
o.pingTimeout = d
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithBackupRetention sets how many .bak.* copies are kept per original
|
|
// configuration file. The oldest backups beyond this number are pruned each
|
|
// time a backend saves. Pass 0 (or a negative value) to disable pruning and
|
|
// keep every backup.
|
|
func WithBackupRetention(n int) Option {
|
|
return func(o *configOptions) {
|
|
o.backupRetention = n
|
|
}
|
|
}
|
|
|
|
// WithAllowPrimaryRemoval permits RemoveAddress to remove an address that is
|
|
// the primary of its family on its interface. By default removing the primary
|
|
// is refused so a caller cannot accidentally change the system's source
|
|
// address or leave a control panel without a main IP; the intended flow is to
|
|
// SetPrimaryAddress on another IP first. Enable this only when you are
|
|
// deliberately tearing down the current primary.
|
|
func WithAllowPrimaryRemoval(allowed bool) Option {
|
|
return func(o *configOptions) {
|
|
o.allowPrimaryRemoval = allowed
|
|
}
|
|
}
|
|
|
|
// WithSkipPanels makes the configurator skip all control-panel backends
|
|
// (cPanel, Plesk, InterWorx) during mutating operations. When enabled,
|
|
// AddAddress, SetPrimaryAddress, and RemoveAddress do not tell the panels to
|
|
// reload, set the main IP, or release an IP; only the running system and the
|
|
// network-manager configuration files are changed. This is intended for
|
|
// maintenance paths where the panel must be left untouched, or where the panel
|
|
// is expected to be reconciled separately.
|
|
func WithSkipPanels(skip bool) Option {
|
|
return func(o *configOptions) {
|
|
o.skipPanels = skip
|
|
}
|
|
}
|
|
|
|
// WithAllowNoBackends lets NewConfigurator return a configurator on a Linux
|
|
// host where backend detection found no network configuration backend. By
|
|
// default that is an error: netlink would apply an address change to the
|
|
// running system, but with nothing to write it to the change would not survive
|
|
// a reboot, and the caller would never be told. Enable this when the
|
|
// configurator is only used to read state (GetInterfaces), or when the caller
|
|
// accepts runtime-only changes.
|
|
func WithAllowNoBackends(allowed bool) Option {
|
|
return func(o *configOptions) {
|
|
o.allowNoBackends = allowed
|
|
}
|
|
}
|
|
|
|
// WithServiceReadyTimeout bounds how long NewConfigurator waits for a backend
|
|
// whose configuration is made through a daemon rather than a file. Today that
|
|
// is NetworkManager: it is configured over D-Bus, so a configurator built
|
|
// before the daemon owns its bus name would find no connections to change.
|
|
//
|
|
// This matters at boot. A host that starts this program from a unit ordered
|
|
// alongside NetworkManager, rather than after it, reaches backend detection
|
|
// while the daemon is still starting; without the wait it would either fail to
|
|
// register the backend at all, or register one that reports an empty interface
|
|
// list. The wait ends as soon as the daemon answers and reports it has finished
|
|
// starting up, so a host where it is already running pays nothing.
|
|
//
|
|
// A timeout of zero or less does not wait: detection probes once and proceeds,
|
|
// which is the right choice for a caller that has already ordered itself after
|
|
// the daemon and would rather fail fast than block.
|
|
func WithServiceReadyTimeout(timeout time.Duration) Option {
|
|
return func(o *configOptions) {
|
|
o.serviceReadyTimeout = timeout
|
|
}
|
|
}
|