docs: add device internal documentation

Add documentation about the implementation of NMDevice.
This commit is contained in:
Beniamino Galvani 2023-06-27 14:05:04 +02:00
parent c7d5caf81e
commit 2b8197d0dc
3 changed files with 417 additions and 0 deletions

View file

@ -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="&lt;div&gt;NEED_AUTH&lt;/div&gt;" 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

322
docs/internal/device.md Normal file
View file

@ -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.