diff options
author | Beniamino Galvani <bgalvani@redhat.com> | 2023-06-27 14:05:04 +0200 |
---|---|---|
committer | Beniamino Galvani <bgalvani@redhat.com> | 2023-06-27 14:29:17 +0200 |
commit | 2b8197d0dcebdfa743073f1bc0f6c778685ff530 (patch) | |
tree | e906efe26ec6804a70c2eeabb25a39797f1ed5ce | |
parent | c7d5caf81e60d2fa5196358575020b55d56f4d63 (diff) |
docs: add device internal documentation
Add documentation about the implementation of NMDevice.
-rw-r--r-- | docs/internal/device-state-machine.drawio | 95 | ||||
-rw-r--r-- | docs/internal/device-state-machine.png | bin | 0 -> 51500 bytes | |||
-rw-r--r-- | docs/internal/device.md | 322 |
3 files changed, 417 insertions, 0 deletions
diff --git a/docs/internal/device-state-machine.drawio b/docs/internal/device-state-machine.drawio new file mode 100644 index 0000000000..bc2bbdf46e --- /dev/null +++ b/docs/internal/device-state-machine.drawio @@ -0,0 +1,95 @@ +<mxfile host="app.diagrams.net" modified="2023-05-30T11:46:47.954Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0" etag="UakFyrM5T_t4qRk7XAKa" version="21.3.5" type="device"> + <diagram name="Page-1" id="c7558073-3199-34d8-9f00-42111426c3f3"> + <mxGraphModel dx="1274" dy="642" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" background="none" math="0" shadow="0"> + <root> + <mxCell id="0" /> + <mxCell id="1" parent="0" /> + <mxCell id="sYMs0qAJREgZJI4c5qmF-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-96" target="gZJxVnoq3P2KiHfPSKmg-103" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="250" y="150" /> + <mxPoint x="250" y="437" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-96" value="FAILED" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="490" y="120" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-97" target="gZJxVnoq3P2KiHfPSKmg-107" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-97" value="SECONDARIES" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="640" y="490" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-98" target="gZJxVnoq3P2KiHfPSKmg-97" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-98" value="IP_CHECK" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="640" y="385" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-99" target="gZJxVnoq3P2KiHfPSKmg-101" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-99" value="<div>NEED_AUTH</div>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="340" y="385" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-100" target="gZJxVnoq3P2KiHfPSKmg-98" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-100" value="IP_CONFIG" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="640" y="280" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-101" target="gZJxVnoq3P2KiHfPSKmg-100" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-101" value="CONFIG" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="340" y="490" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-102" target="gZJxVnoq3P2KiHfPSKmg-99" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-102" value="PREPARE" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="340" y="280" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-103" target="gZJxVnoq3P2KiHfPSKmg-102" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-103" value="DISCONNECTED" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="80" y="422" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-104" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="140" y="419" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-104" value="UNAVAILABLE" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="80" y="276" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-105" target="gZJxVnoq3P2KiHfPSKmg-104" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-105" value="UNMANAGED" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="80" y="129" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-106" target="gZJxVnoq3P2KiHfPSKmg-103" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-106" value="DEACTIVATING" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="80" y="569" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="gZJxVnoq3P2KiHfPSKmg-107" target="gZJxVnoq3P2KiHfPSKmg-106" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="gZJxVnoq3P2KiHfPSKmg-107" value="ACTIVATED" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> + <mxGeometry x="490" y="621" width="120" height="60" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="sYMs0qAJREgZJI4c5qmF-14" target="gZJxVnoq3P2KiHfPSKmg-96" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="sYMs0qAJREgZJI4c5qmF-14" value="Device activation" style="swimlane;whiteSpace=wrap;html=1;startSize=30;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" parent="1" vertex="1"> + <mxGeometry x="310" y="230" width="480" height="460" as="geometry" /> + </mxCell> + </root> + </mxGraphModel> + </diagram> +</mxfile> diff --git a/docs/internal/device-state-machine.png b/docs/internal/device-state-machine.png Binary files differnew file mode 100644 index 0000000000..35eca6c4cd --- /dev/null +++ b/docs/internal/device-state-machine.png diff --git a/docs/internal/device.md b/docs/internal/device.md new file mode 100644 index 0000000000..0042fdc55b --- /dev/null +++ b/docs/internal/device.md @@ -0,0 +1,322 @@ +Devices +======= + +What is a device +---------------- + +In NetworkManager, a device represents an object that allows some sort +of network configuration; it can be a regular Linux network interface +(physical as Ethernet or Wi-Fi; or virtual as a bridge or a VLAN), but +it can also be an entity that does not have a link object in the +kernel; examples of the latter kind are modems and OVS bridges/ports. + +NetworkManager automatically creates device objects at runtime based +on the device found on the system. It also creates special devices, +called *unrealized* devices that represent potential devices, +e.g. that don't exist yet but will exist when a given connection gets +activated. See the section "Unrealized devices" for more details. + +Each device has several properties; the most important are: + + - `iface`: the name of the interface. + + - `ifindex`: for devices backed by a kernel link, this is the kernel + interface index. + + - `ip_ifindex`: some devices have multiple kernel link associated. In + such case, `ifindex` is the index of the base link, while + `ip_ifindex` is the index of the link on which IP configuration + should be made. For example when activating a PPPoE connection, the + device has `ifindex` referring to the Ethernet link and + `ip_ifindex` to the PPP one. + + - `state`: the current state of the device in the device state + machine, see the next sections. + + - `l3cfg`: the L3Cfg instance takes care of configuring IP on one + ifindex. + +The device object also is exposed on D-Bus, with properties, methods +and signals, see the `interface_info_device` structure. + +Activation +---------- + +To configure a device, NetworkManager needs to activate a connection +profile on it. This happens for two reasons: + - it was requested by the user via the `ActivateConnection()` and + `AddAndActivateConnection()` API methods, which are handled by + functions `impl_manager_activate_connection()` and + `impl_manager_add_and_activate_connection()`; + - it is the result of an internal decision; in this case the + activation is handled by `nm_manager_activate_connection()`; the + reason can be: + - the connection profile is activated automatically (for example, + at startup) because it is configured to auto-connect; + - the connection profile is being activated as a dependency of + another profile; for example: + - a port profile (e.g. bridge port) always depends on the + corresponding controller profile (e.g. bridge); + - a controller profile can be configured to automatically + activate port profiles; + - during a checkpoint rollback; + - etc. + +The activation starts by first creating a `NMActiveConnection` +object. This is a abstract type used to track the state of the +activation on a specific device; it has two implementations: +`NMActRequest` for regular devices and `NMVpnConnection` for VPNs. + +Two important fields of an active connection object are: + + - the *settings-connection*: this is a pointer to the connection + being activated; it points to the connection in `NMSettings` and + always reflects the latest changes in the profile; + + - the *applied-connection*: similar to the *settings-connection*, but + this is a copy of the original *settings-connection* done at the + time the activation started. During the current activation, the + properties to configure are always read from this applied + connection because they shouldn't change even if the profile is + modified. + +Unrealized devices +------------------ + +We said that to start an activation we need a profile and a +device. What happens if the device doesn't already exist because it is +a virtual one (such as a bridge or a vlan)? + +This problem is currently solved in NetworkManager by having a special +kind of devices, *unrealized* devices. Those are 'potential' devices, +that don't exist in kernel; they are created to represent the device +for each virtual profile. Technically speaking, a device is unrealized +when the `real` flag is set to zero in the device private struct. + +When NetworkManager decides to activate a virtual profile, the +corresponding device gets realized by calling +`nm_device_create_and_realize()` and then the activation proceeds as +for physical devices. + +Object hierarchy +---------------- + +In the NetworkManager code a device is a `NMDevice` object, which has +several subclasses; each subclass represents a specific kind of device +(`NMDeviceEthernet`, `NMDeviceWifi`, `NMDeviceBridge`, etc.), and can +reimplement properties and methods of the superclass to customize the +behavior (see `struct _NMDeviceClass`). + +To show how this is used in practice, let's look at function +`nm_device_is_available()`, which indicates whether a device is ready +to be activated. The function calls the virtual method `is_available()`: + +```C +gboolean +nm_device_is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags) +{ + ... + return NM_DEVICE_GET_CLASS(self)->is_available(self, flags); +} +``` + +`NMDevice` has a generic implementation of that method which performs +generic checks: + +```C +static gboolean +is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + + if (priv->carrier || priv->ignore_carrier) + return TRUE; + ... + + return FALSE; +} +``` + +A Ethernet device needs additional checks and so it reimplements the +method to check that the MAC address is already set: + +```C +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + if (!NM_DEVICE_CLASS(nm_device_ethernet_parent_class) + ->is_available(device, flags)) + return FALSE; + + return !!nm_device_get_initial_hw_address(device); +} +``` + +Note how the function first chains up by calling the `NMDevice` +implementation and then it performs an additional check. This pattern +is heavily used in NetworkManager. + +Device states +------------- + +Each device behaves according to a state machine that looks like this: + +![Device state machine](device-state-machine.png "Device state machine") + +While the implementation of the state machine is done in different +functions in `nm-device.c`, the core part is in `_set_state_full()`. + +The states are: + + * UNMANAGED: this is the initial state and means that the device is + not managed by NetworkManager. The unmanaged state is tracked via a + bitmap of flags, and the device stays in this state while at least + one flag is set. See the section "Unmanaged flags" below for more + details. Once all unmanaged flags are cleared, the device is ready + to become UNAVAILABLE. The state transition is done in function + `_set_unmanaged_flags()`, which changes the states to UNMANAGED or + UNAVAILABLE depending on the value of the flags. Note that even if + it's not displayed in the diagram, the UNMANAGED state can be + reached by virtually every other state when a unmanaged flag + becomes set. + + * UNAVAILABLE: the device is managed by NetworkManager, but is not + available for use. Reasons may include the wireless switched off, + missing firmware, no ethernet carrier, missing supplicant or modem + manager, etc. When a device becomes available, it can transition to + DISCONNECTED; this decision is taken in various places by + scheduling a check via `nm_device_queue_recheck_available()`. + + * DISCONNECTED: the device can be activated, but is currently + idle and not connected to a network. When entering this state from + a state that belongs to the activation sequence or from + FAILED/DEACTIVATING, a cleanup of previous configuration is + done. If there is an activation queued, it's started; otherwise, + `NMPolicy` reacts to the state change and calls + `nm_policy_device_recheck_auto_activate_schedule()` to check if + there a connection that can be auto-activated on the device. + + * PREPARE: this is the first state of an activation; in this + state some initial operation are performed, such as changing the + MAC address, setting physical link properties, and anything else + required to connect to the requested network. + + This state is entered via + `nm_device_activate_schedule_stage1_device_prepare()`. When + finished, `nm_device_activate_schedule_stage2_device_config()` is + used to transition to the CONFIG state. Those functions are + re-entrant, in the sense that when a device is in a given state and + needs that an operation completes (or a condition becomes true), it + can wait and then invoke the same function again; in that way it + re-enters the same state, where all the conditions are evaluated + again and if possible the device will transition to the next state. + + * CONFIG: the device is connecting to the requested network. + This may include operations like associating with the Wi-Fi AP, + dialing the modem, connecting to the remote Bluetooth device, etc. + + * NEED_AUTH: the device requires more information to continue + connecting to the requested network. This includes secrets like + WiFi passphrases, login passwords, PIN codes, etc. + + * IP_CONFIG: this state is entered via + `nm_device_activate_schedule_stage3_ip_config()`, and is where IP + addresses and routes are assigned to the device. Function + `_dev_ip_state_check()` checks that the configuration is terminated + according the connection configuration; if so, it moves the device + to state IP_CHECK. + + * IP_CHECK: in this state, NetworkManager waits that the gateway + can be pinged successfully if the property + `connection.gateway-ping-timeout` is set. By default this step is a + no op since the property is unset. After the optional ping, the + dispatcher `pre-up` event is emitted, and the device goes to + SECONDARIES. + + * SECONDARIES: connections have a `connection.secondaries` property + that specifies a list of UUID of connections of type VPN that can + be activated automatically when the connection goes up. If there + are any secondaries, they are activated in this stage; note that + since this operation involves other devices, it is done in + `NMPolicy`, upon the emission of the state change signal handled in + `nm-policy.c:device_state_changed()`. After any secondaries are + activated, the devices transitions to state ACTIVATED. + + * ACTIVATED: the device has a network connection. Upon entering this + state the device emits the `up` dispatcher event. The device + remains is this state until the connection is deactivated or until + it fails. + + * DEACTIVATING: a disconnection from the current network + connection was requested, and the device is cleaning up resources + used for that connection. In this state the `pre-down` dispatcher + event is emitted. When finished, the devices goes again to + DISCONNECTED and the `down` dispatch event is fired. + + * FAILED: the device failed to connect to the requested network + and is cleaning up the connection request. This state can be + reached from any state belonging to the activation. When the + cleanup is done, the device goes to DISCONNECTED. If the device was + previously activated, the `down` dispatcher event is emitted. + +Unmanaged devices +----------------- + +Each device has a mask of flags representing reasons why the device is +unmanaged; when at least of of those flags is set, the device goes to +state UNMANAGED. When all flags are cleared, the device moves to state +UNAVAILABLE. The unmanaged flags are currently: + + - SLEEPING: the system is suspended, or networking is disabled + + - QUITTING: NetworkManager is shutting down. + + - PLATFORM_INIT: NetworkManager is waiting for udev to announce + the device. Note that NetworkManager can't touch the device until + then because udev might perform operations on it (such as renaming + or changing the MAC). Unrealized devices (see later) have this + flag set. + + - USER_EXPLICIT: when unmanaged by explicit user decision + (e.g. via a D-Bus command). + + - USER_SETTINGS: when unmanaged by user decision via the + settings plugin (for example `keyfile.unmanaged-devices` or + ifcfg-rh's `NM_CONTROLLED=no`). Although this is + user-configuration it cannot be overruled and is + authoritative. That is because users may depend on dropping a + ifcfg-rh file to ensure the device is unmanaged. + + - USER_CONF: when unmanaged by user decision via the + NetworkManager.conf ("unmanaged" in the [device] + section). Contrary to USER_SETTINGS, this can be overwritten via + D-Bus. + + - BY_DEFAULT: this flag is no longer used. + + - USER_UDEV: unmanaged via a udev rule. + + - EXTERNAL_DOWN: unmanaged because the interface was not created by + NetworkManager and is currently down. + +Note that the unmanaged flags are tracked via two variables +`unmanaged_mask` and `unmanaged_flags`; in this way each flag is in +practice a tri-state variable with possible values TRUE (unmanaged), +FALSE (managed) and UNSET. + +External devices and sys-iface-state +------------------------------------ + +Even if a device is managed, that doesn't mean that NetworkManager is +actively configuring it. When a device is created externally (for +example via `ip link`) and has an IP configuration, NetworkManager +creates a in-memory connection representing the configuration +parameters on the interface such as IP addresses, routes, DNS, etc.; +the connection appears as active but NetworkManager doesn't actually +touch the interface. The external status is tracked in the +`sys-iface-state` member, which can have the following values: + + - EXTERNAL: the interface is not touched by NM. + - ASSUME: this value is deprecated; it used to mean that NM should manage the device without fully reconfiguring it. Now, the interface is either managed on external. + - MANAGED: the interface is fully managed. + - REMOVED: the link was removed externally. |