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 } }