network: add ModemManager support

[Match]
Name=wwan*

[Network]
LLDP=no
LinkLocalAddressing=no
IPv6AcceptRA=no

[ModemManager]
SimpleConnectPropertie]s=apn=internet ip-type=ipv4 allow-roaming=no pin=1111 operator-id=25503
RouteMetric=200
UseGateway=yes

Co-authored-by: Yu Watanabe <watanabe.yu+github@gmail.com>
This commit is contained in:
Oleksandr Andrushchenko
2026-02-16 13:22:21 +09:00
committed by Yu Watanabe
co-authored by Yu Watanabe
parent 4ed1e2ea17
commit f8a4c3d375
20 changed files with 2368 additions and 9 deletions
+134
View File
@@ -6444,6 +6444,117 @@ ServerAddress=192.168.0.1/24</programlisting>
</variablelist>
</refsect1>
<refsect1>
<title>[ModemManager] Section Options</title>
<para>This section configures the default setting of the ModemManager integration. See
<ulink url="https://modemmanager.org/docs/modemmanager/" /> for more information about ModemManager.</para>
<para>Regardless of the [ModemManager] section settings consider using the following for LTE modems (take into account
that LTE modems do not typically support LLDP because LLDP is a Layer 2 protocol for Ethernet networks and an LTE
modem connects to a cellular network, not a local Ethernet LAN):
<programlisting>[Network]
LLDP=no
LinkLocalAddressing=no
IPv6AcceptRA=no
</programlisting>
</para>
<para>The following options are available in the [ModemManager] section:</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>SimpleConnectProperties=</varname></term>
<listitem>
<para>Specifies the white-space separated list of simple connect properties used to connect a modem. See
<ulink url="https://www.freedesktop.org/software/ModemManager/man/latest/mmcli.1.html" /> for more
information about simple connect. If no properties provided then the connection is not initiated.</para>
<varlistentry>
<term><option>apn</option>=<replaceable>NAME</replaceable></term>
<listitem><para>An Access Point Name (APN) is the name of a gateway between a mobile network
(GSM, GPRS, 3G, 4G and 5G) and another computer network. Required in 3GPP.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>allowed-auth</option>=<replaceable>METHOD</replaceable></term>
<listitem><para>Authentication method to use. Takes one of "none", "pap", "chap", "mschap", "mschapv2" or "eap".
Optional in 3GPP.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>user</option>=<replaceable>NAME</replaceable></term>
<listitem><para>User name (if any) required by the network. Optional in 3GPP.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>password</option>=<replaceable>PASSWORD</replaceable></term>
<listitem><para>Password (if any) required by the network. Optional in 3GPP.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>ip-type</option>=<replaceable>TYPE</replaceable></term>
<listitem><para>Addressing type. Takes one of "none", "ipv4", "ipv6", "ipv4v6" or "any".
Optional in 3GPP and CDMA.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>allow-roaming</option>=<replaceable>BOOL</replaceable></term>
<listitem><para>A boolean. When true, connection is allowed during roaming. When false,
connection is not allowed during roaming. Optional in 3GPP.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>pin</option>=<replaceable>PIN</replaceable></term>
<listitem><para>SIM-PIN unlock code.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>operator-id</option>=<replaceable>ID</replaceable></term>
<listitem><para>ETSI MCC-MNC of a network to force registration.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RouteMetric=</varname></term>
<listitem>
<para>Set the routing metric for routes specified by the mobile network (including the prefix route
added for the specified prefix). Takes an unsigned integer in the range 0…4294967295.
When unset or set to 0, the kernel's default value will be used.</para>
<xi:include href="version-info.xml" xpointer="v260"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseGateway=</varname></term>
<listitem>
<para>When true (the default), the router address will be configured as the default gateway.</para>
<xi:include href="version-info.xml" xpointer="v260"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
@@ -6771,6 +6882,29 @@ Xfrm=xfrm0</programlisting>
This allows hardware based ipsec offloading to the <literal>eth0</literal> nic.
If offloading is not needed, xfrm interfaces can be assigned to the <literal>lo</literal> device.
</para>
</example>
<example>
<title>Connecting to a cellular network with ModemManager</title>
<programlisting># /etc/systemd/network/27-wwan0.network
[Match]
Name=wwan0
[Network]
LLDP=no
LinkLocalAddressing=no
IPv6AcceptRA=no
[ModemManager]
SimpleConnectProperties=apn=internet pin=1111
RouteMetric=30
UseGateway=yes</programlisting>
<para>This connects a cellular modem to a broadband network matched with the network interface <literal>wwan0</literal>,
with APN name <literal>internet</literal>, SIM card pin unlock code <literal>1111</literal> and sets up a default
gateway with route metric of 30.
</para>
</example>
</refsect1>
+2
View File
@@ -89,6 +89,8 @@ systemd_networkd_extract_sources = files(
'networkd-util.c',
'networkd-wifi.c',
'networkd-wiphy.c',
'networkd-wwan.c',
'networkd-wwan-bus.c',
'tc/cake.c',
'tc/codel.c',
'tc/drr.c',
+4
View File
@@ -29,6 +29,7 @@
#include "networkd-route.h"
#include "networkd-setlink.h"
#include "networkd-state-file.h"
#include "networkd-wwan.h"
#include "parse-util.h"
#include "set.h"
#include "socket-util.h"
@@ -1770,6 +1771,9 @@ int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) {
if (!link->dhcp_client)
return 0;
if (link_dhcp_enabled_by_bearer(link, AF_INET) == 0)
return 0;
if (!link_has_carrier(link))
return 0;
+4
View File
@@ -24,6 +24,7 @@
#include "networkd-queue.h"
#include "networkd-route.h"
#include "networkd-state-file.h"
#include "networkd-wwan.h"
#include "set.h"
#include "string-table.h"
#include "string-util.h"
@@ -494,6 +495,9 @@ int dhcp6_start(Link *link) {
if (!link_dhcp6_enabled(link))
return 0;
if (link_dhcp_enabled_by_bearer(link, AF_INET6) == 0)
return 0;
if (!link_has_carrier(link))
return 0;
+10
View File
@@ -26,6 +26,7 @@
#include "networkd-route.h"
#include "networkd-route-util.h"
#include "networkd-routing-policy-rule.h"
#include "networkd-wwan.h"
#include "ordered-set.h"
#include "set.h"
#include "string-util.h"
@@ -541,6 +542,15 @@ static int dns_append_json(Link *link, sd_json_variant **v) {
return r;
}
Bearer *b;
if (link_get_bearer(link, &b) >= 0)
FOREACH_ARRAY(dns, b->dns, b->n_dns) {
r = dns_append_json_one(link, *dns, NETWORK_CONFIG_SOURCE_MODEM_MANAGER, NULL, &array);
if (r < 0)
return r;
}
if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
const struct in_addr *dns;
union in_addr_union s;
+9 -1
View File
@@ -63,6 +63,7 @@
#include "networkd-state-file.h"
#include "networkd-sysctl.h"
#include "networkd-wifi.h"
#include "networkd-wwan-bus.h"
#include "ordered-set.h"
#include "parse-util.h"
#include "set.h"
@@ -529,6 +530,9 @@ void link_check_ready(Link *link) {
if (!link->sr_iov_configured)
return (void) log_link_debug(link, "%s(): SR-IOV is not configured.", __func__);
if (!link->bearer_configured)
return (void) log_link_debug(link, "%s(): Bearer has not been applied.", __func__);
/* IPv6LL is assigned after the link gains its carrier. */
if (!link->network->configure_without_carrier &&
link_ipv6ll_enabled(link) &&
@@ -538,7 +542,7 @@ void link_check_ready(Link *link) {
/* All static addresses must be ready. */
bool has_static_address = false;
SET_FOREACH(a, link->addresses) {
if (a->source != NETWORK_CONFIG_SOURCE_STATIC)
if (!IN_SET(a->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_MODEM_MANAGER))
continue;
if (!address_is_ready(a))
return (void) log_link_debug(link, "%s(): static address %s is not ready.", __func__,
@@ -1293,6 +1297,10 @@ static int link_configure(Link *link) {
if (r < 0)
return r;
r = link_modem_reconfigure(link);
if (r < 0)
return r;
if (!link_has_carrier(link))
return 0;
+2
View File
@@ -108,6 +108,7 @@ typedef struct Link {
unsigned set_link_messages;
unsigned set_flags_messages;
unsigned create_stacked_netdev_messages;
unsigned bearer_messages;
Set *addresses;
Set *neighbors;
@@ -141,6 +142,7 @@ typedef struct Link {
bool master_set:1;
bool stacked_netdevs_created:1;
bool bridge_vlan_set:1;
bool bearer_configured:1;
sd_dhcp_server *dhcp_server;
+8
View File
@@ -46,6 +46,7 @@
#include "networkd-state-file.h"
#include "networkd-wifi.h"
#include "networkd-wiphy.h"
#include "networkd-wwan-bus.h"
#include "ordered-set.h"
#include "qdisc.h"
#include "set.h"
@@ -96,6 +97,8 @@ static int on_connected(sd_bus_message *message, void *userdata, sd_bus_error *r
if (m->product_uuid_requested)
(void) manager_request_product_uuid(m);
(void) manager_notify_mm_bus_connected(m);
return 0;
}
@@ -145,6 +148,8 @@ static int manager_connect_bus(Manager *m) {
if (r < 0)
log_warning_errno(r, "Failed to request match for PrepareForSleep, ignoring: %m");
(void) manager_match_mm_signals(m);
return 0;
}
@@ -733,6 +738,9 @@ Manager* manager_free(Manager *m) {
set_free(m->rules);
sd_bus_slot_unref(m->slot_mm);
hashmap_free(m->modems_by_path);
sd_netlink_unref(m->rtnl);
sd_netlink_unref(m->genl);
sd_netlink_unref(m->nfnl);
+4
View File
@@ -99,6 +99,10 @@ typedef struct Manager {
Hashmap *wiphy_by_index;
Hashmap *wiphy_by_name;
/* ModemManager support */
sd_bus_slot *slot_mm;
Hashmap *modems_by_path;
/* For link speed meter */
bool use_speed_meter;
sd_event_source *speed_meter_event_source;
+4
View File
@@ -40,6 +40,7 @@ _Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"")
#include "networkd-radv.h"
#include "networkd-route.h"
#include "networkd-routing-policy-rule.h"
#include "networkd-wwan.h"
#include "qdisc.h"
#include "socket-util.h"
#include "tclass.h"
@@ -492,6 +493,9 @@ CAN.ClassicDataLengthCode, config_parse_can_control_mode,
CAN.Termination, config_parse_can_termination, 0, 0
IPoIB.Mode, config_parse_ipoib_mode, 0, offsetof(Network, ipoib_mode)
IPoIB.IgnoreUserspaceMulticastGroups, config_parse_tristate, 0, offsetof(Network, ipoib_umcast)
ModemManager.SimpleConnectProperties, config_parse_strv, 0, offsetof(Network, mm_simple_connect_props)
ModemManager.RouteMetric, config_parse_mm_route_metric, 0, 0
ModemManager.UseGateway, config_parse_tristate, 0, offsetof(Network, mm_use_gateway)
QDisc.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0
QDisc.Handle, config_parse_qdisc_handle, _QDISC_KIND_INVALID, 0
BFIFO.Parent, config_parse_qdisc_parent, QDISC_KIND_BFIFO, 0
+6
View File
@@ -512,6 +512,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.ipoib_mode = _IP_OVER_INFINIBAND_MODE_INVALID,
.ipoib_umcast = -1,
.mm_use_gateway = -1,
};
r = config_parse_many_full(
@@ -551,6 +553,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
"LLDP\0"
"TrafficControlQueueingDiscipline\0"
"CAN\0"
"ModemManager\0"
"QDisc\0"
"BFIFO\0"
"CAKE\0"
@@ -847,6 +850,9 @@ static Network *network_free(Network *network) {
hashmap_free(network->qdiscs_by_section);
hashmap_free(network->tclasses_by_section);
/* ModemManager */
strv_free(network->mm_simple_connect_props);
return mfree(network);
}
+6
View File
@@ -414,6 +414,12 @@ typedef struct Network {
/* NTP */
char **ntp;
/* ModemManager support */
char **mm_simple_connect_props;
int mm_use_gateway;
uint32_t mm_route_metric;
bool mm_route_metric_set;
} Network;
DECLARE_TRIVIAL_REF_UNREF_FUNC(Network, network);
+14
View File
@@ -23,6 +23,7 @@
#include "networkd-network.h"
#include "networkd-ntp.h"
#include "networkd-state-file.h"
#include "networkd-wwan.h"
#include "ordered-set.h"
#include "set.h"
#include "string-util.h"
@@ -109,6 +110,14 @@ static int link_put_dns(Link *link, OrderedSet **s) {
if (r < 0)
return r;
Bearer *b;
if (link_get_bearer(link, &b) >= 0) {
r = ordered_set_put_dns_servers(s, link->ifindex, b->dns, b->n_dns);
if (r < 0)
return r;
}
if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
const struct in_addr *addresses;
@@ -801,6 +810,11 @@ static int link_save(Link *link) {
space = false;
link_save_dns(link, f, link->network->dns, link->network->n_dns, &space);
Bearer *b;
if (link_get_bearer(link, &b) >= 0)
link_save_dns(link, f, b->dns, b->n_dns, &space);
/* DNR resolvers are not required to provide Do53 service, however resolved doesn't
* know how to handle such a server so for now Do53 service is required, and
* assumed. */
+9 -8
View File
@@ -17,14 +17,15 @@
/* This is used in log messages, and never used in parsing settings. So, upper cases are OK. */
static const char * const network_config_source_table[_NETWORK_CONFIG_SOURCE_MAX] = {
[NETWORK_CONFIG_SOURCE_FOREIGN] = "foreign",
[NETWORK_CONFIG_SOURCE_STATIC] = "static",
[NETWORK_CONFIG_SOURCE_IPV4LL] = "IPv4LL",
[NETWORK_CONFIG_SOURCE_DHCP4] = "DHCPv4",
[NETWORK_CONFIG_SOURCE_DHCP6] = "DHCPv6",
[NETWORK_CONFIG_SOURCE_DHCP_PD] = "DHCP-PD",
[NETWORK_CONFIG_SOURCE_NDISC] = "NDisc",
[NETWORK_CONFIG_SOURCE_RUNTIME] = "runtime",
[NETWORK_CONFIG_SOURCE_FOREIGN] = "foreign",
[NETWORK_CONFIG_SOURCE_STATIC] = "static",
[NETWORK_CONFIG_SOURCE_IPV4LL] = "IPv4LL",
[NETWORK_CONFIG_SOURCE_DHCP4] = "DHCPv4",
[NETWORK_CONFIG_SOURCE_DHCP6] = "DHCPv6",
[NETWORK_CONFIG_SOURCE_DHCP_PD] = "DHCP-PD",
[NETWORK_CONFIG_SOURCE_NDISC] = "NDisc",
[NETWORK_CONFIG_SOURCE_RUNTIME] = "runtime",
[NETWORK_CONFIG_SOURCE_MODEM_MANAGER] = "ModemManager",
};
DEFINE_STRING_TABLE_LOOKUP(network_config_source, NetworkConfigSource);
+1
View File
@@ -14,6 +14,7 @@ typedef enum NetworkConfigSource {
NETWORK_CONFIG_SOURCE_DHCP_PD,
NETWORK_CONFIG_SOURCE_NDISC,
NETWORK_CONFIG_SOURCE_RUNTIME, /* through D-Bus method */
NETWORK_CONFIG_SOURCE_MODEM_MANAGER,
_NETWORK_CONFIG_SOURCE_MAX,
_NETWORK_CONFIG_SOURCE_INVALID = -EINVAL,
} NetworkConfigSource;
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef struct Manager Manager;
typedef struct Link Link;
/* From ModemManager-enums.h */
typedef enum {
MM_BEARER_IP_FAMILY_NONE = 0,
MM_BEARER_IP_FAMILY_IPV4 = 1 << 0,
MM_BEARER_IP_FAMILY_IPV6 = 1 << 1,
MM_BEARER_IP_FAMILY_IPV4V6 = 1 << 2,
MM_BEARER_IP_FAMILY_ANY = 0xFFFFFFFF,
} MMBearerIpFamily;
typedef enum {
MM_BEARER_TYPE_UNKNOWN = 0,
MM_BEARER_TYPE_DEFAULT = 1,
MM_BEARER_TYPE_DEFAULT_ATTACH = 2,
MM_BEARER_TYPE_DEDICATED = 3,
} MMBearerType;
typedef enum {
MM_MODEM_STATE_FAILED = -1,
MM_MODEM_STATE_UNKNOWN = 0,
MM_MODEM_STATE_INITIALIZING = 1,
MM_MODEM_STATE_LOCKED = 2,
MM_MODEM_STATE_DISABLED = 3,
MM_MODEM_STATE_DISABLING = 4,
MM_MODEM_STATE_ENABLING = 5,
MM_MODEM_STATE_ENABLED = 6,
MM_MODEM_STATE_SEARCHING = 7,
MM_MODEM_STATE_REGISTERED = 8,
MM_MODEM_STATE_DISCONNECTING = 9,
MM_MODEM_STATE_CONNECTING = 10,
MM_MODEM_STATE_CONNECTED = 11,
} MMModemState;
typedef enum { /*< underscore_name=mm_modem_state_failed_reason >*/
MM_MODEM_STATE_FAILED_REASON_NONE = 0,
MM_MODEM_STATE_FAILED_REASON_UNKNOWN = 1,
MM_MODEM_STATE_FAILED_REASON_SIM_MISSING = 2,
MM_MODEM_STATE_FAILED_REASON_SIM_ERROR = 3,
MM_MODEM_STATE_FAILED_REASON_UNKNOWN_CAPABILITIES = 4,
MM_MODEM_STATE_FAILED_REASON_ESIM_WITHOUT_PROFILES = 5,
__MM_MODEM_STATE_FAILED_REASON_MAX = 6,
} MMModemStateFailedReason;
typedef enum {
MM_BEARER_IP_METHOD_UNKNOWN = 0,
MM_BEARER_IP_METHOD_PPP = 1,
MM_BEARER_IP_METHOD_STATIC = 2,
MM_BEARER_IP_METHOD_DHCP = 3,
} MMBearerIpMethod;
typedef enum { /*< underscore_name=mm_modem_port_type >*/
MM_MODEM_PORT_TYPE_UNKNOWN = 1,
MM_MODEM_PORT_TYPE_NET = 2,
MM_MODEM_PORT_TYPE_AT = 3,
MM_MODEM_PORT_TYPE_QCDM = 4,
MM_MODEM_PORT_TYPE_GPS = 5,
MM_MODEM_PORT_TYPE_QMI = 6,
MM_MODEM_PORT_TYPE_MBIM = 7,
MM_MODEM_PORT_TYPE_AUDIO = 8,
MM_MODEM_PORT_TYPE_IGNORED = 9,
MM_MODEM_PORT_TYPE_XMMRPC = 10,
} MMModemPortType;
typedef enum { /*< underscore_name=mm_bearer_allowed_auth >*/
MM_BEARER_ALLOWED_AUTH_UNKNOWN = 0,
/* bits 0..4 order match Ericsson device bitmap */
MM_BEARER_ALLOWED_AUTH_NONE = 1 << 0,
MM_BEARER_ALLOWED_AUTH_PAP = 1 << 1,
MM_BEARER_ALLOWED_AUTH_CHAP = 1 << 2,
MM_BEARER_ALLOWED_AUTH_MSCHAP = 1 << 3,
MM_BEARER_ALLOWED_AUTH_MSCHAPV2 = 1 << 4,
MM_BEARER_ALLOWED_AUTH_EAP = 1 << 5,
} MMBearerAllowedAuth;
typedef enum {
MODEM_RECONNECT_DONE, /* No reconnect is required, e.g. connected. */
MODEM_RECONNECT_SCHEDULED, /* Reconnect is in progress. */
MODEM_RECONNECT_WAITING, /* Waiting for modem to recover. */
} ModemReconnectState;
int manager_notify_mm_bus_connected(Manager *manager);
int manager_match_mm_signals(Manager *manager);
int link_modem_reconfigure(Link *link);
+641
View File
@@ -0,0 +1,641 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "bus-util.h"
#include "hashmap.h"
#include "networkd-address.h"
#include "networkd-dhcp4.h"
#include "networkd-dhcp6.h"
#include "networkd-link.h"
#include "networkd-setlink.h"
#include "networkd-manager.h"
#include "networkd-ndisc.h"
#include "networkd-route.h"
#include "networkd-wwan.h"
#include "parse-util.h"
#include "sd-dhcp-client.h"
#include "sd-dhcp6-client.h"
#include "sd-ndisc.h"
#include "set.h"
#include "string-util.h"
Bearer* bearer_free(Bearer *b) {
if (!b)
return NULL;
if (b->modem) {
if (b->path)
hashmap_remove_value(b->modem->bearers_by_path, b->path, b);
if (b->name)
hashmap_remove_value(b->modem->bearers_by_name, b->name, b);
}
sd_bus_slot_unref(b->slot_getall);
free(b->path);
free(b->name);
free(b->apn);
in_addr_full_array_free(b->dns, b->n_dns);
return mfree(b);
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
bearer_hash_ops,
char,
string_hash_func,
string_compare_func,
Bearer,
bearer_free);
int bearer_new(Modem *modem, const char *path, Bearer **ret) {
_cleanup_(bearer_freep) Bearer *b = NULL;
_cleanup_free_ char *p = NULL;
int r;
assert(modem);
assert(path);
if (hashmap_contains(modem->bearers_by_path, path))
return -EEXIST;
p = strdup(path);
if (!p)
return -ENOMEM;
b = new(Bearer, 1);
if (!b)
return -ENOMEM;
*b = (Bearer) {
.modem = modem,
.path = TAKE_PTR(p),
};
r = hashmap_ensure_put(&modem->bearers_by_path, &bearer_hash_ops, b->path, b);
if (r < 0)
return r;
if (ret)
*ret = b;
TAKE_PTR(b);
return 0;
}
int bearer_set_name(Bearer *b, const char *name) {
Bearer *old;
int r;
assert(b);
assert(b->modem);
assert(name);
if (streq_ptr(b->name, name))
return 0;
if (b->name)
hashmap_remove_value(b->modem->bearers_by_name, b->name, b);
if (isempty(name)) {
b->name = mfree(b->name);
return 0;
}
r = free_and_strdup(&b->name, name);
if (r < 0)
return r;
/*
* FIXME: it is possible during reconnect that an interface is already
* registered in the hash map: if simple connect options
* are changed, e.g. externally modified .network file and then
* reloaded with 'networkctl reload'. This may create a new bearer
* attached to the same inerface name, e.g. "wwan0". The order in which
* we parse the bearer properties is undetermined and it can be that we
* need to raplce the old one with the new one now, so only one bearer
* with the given interface name exists.
*/
old = hashmap_get(b->modem->bearers_by_name, name);
if (old) {
hashmap_remove_value(old->modem->bearers_by_name, name, old);
old->name = mfree(old->name);
}
return hashmap_ensure_put(&b->modem->bearers_by_name, &bearer_hash_ops, b->name, b);
}
int bearer_get_by_path(Manager *manager, const char *path, Modem **ret_modem, Bearer **ret_bearer) {
Modem *modem;
Bearer *b;
assert(manager);
assert(path);
HASHMAP_FOREACH(modem, manager->modems_by_path) {
b = hashmap_get(modem->bearers_by_path, path);
if (!b)
continue;
if (ret_bearer)
*ret_bearer = b;
if (ret_modem)
*ret_modem = modem;
return 0;
}
return -ENOENT;
}
Modem* modem_free(Modem *modem) {
if (!modem)
return NULL;
if (modem->bearers_by_name)
hashmap_free(modem->bearers_by_name);
if (modem->bearers_by_path)
hashmap_free(modem->bearers_by_path);
if (modem->manager)
hashmap_remove_value(modem->manager->modems_by_path, modem->path, modem);
sd_bus_slot_unref(modem->slot_propertieschanged);
sd_bus_slot_unref(modem->slot_statechanged);
sd_bus_slot_unref(modem->slot_connect);
free(modem->path);
free(modem->manufacturer);
free(modem->model);
free(modem->port_name);
return mfree(modem);
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
modems_hash_ops,
char,
string_hash_func,
string_compare_func,
Modem,
modem_free);
int modem_new(Manager *m, const char *path, Modem **ret) {
_cleanup_(modem_freep) Modem *modem = NULL;
_cleanup_free_ char *p = NULL;
int r;
assert(m);
assert(path);
if (hashmap_contains(m->modems_by_path, path))
return -EEXIST;
p = strdup(path);
if (!p)
return -ENOMEM;
modem = new(Modem, 1);
if (!modem)
return -ENOMEM;
*modem = (Modem) {
.manager = m,
.path = TAKE_PTR(p),
};
r = hashmap_ensure_put(&m->modems_by_path, &modems_hash_ops, modem->path, modem);
if (r < 0)
return r;
if (ret)
*ret = modem;
TAKE_PTR(modem);
return 0;
}
int modem_get_by_path(Manager *m, const char *path, Modem **ret) {
Modem *modem;
assert(m);
assert(path);
modem = hashmap_get(m->modems_by_path, path);
if (!modem)
return -ENOENT;
if (ret)
*ret = modem;
return 0;
}
int link_get_modem(Link *link, Modem **ret) {
Modem *modem;
assert(link);
assert(link->manager);
assert(link->ifname);
HASHMAP_FOREACH(modem, link->manager->modems_by_path)
if (modem->port_name && streq(modem->port_name, link->ifname)) {
*ret = modem;
return 0;
}
return -ENOENT;
}
int link_get_bearer(Link *link, Bearer **ret) {
Modem *modem;
assert(link);
assert(link->manager);
assert(link->ifname);
HASHMAP_FOREACH(modem, link->manager->modems_by_path) {
Bearer *b;
b = hashmap_get(modem->bearers_by_name, link->ifname);
if (!b)
continue;
if (ret)
*ret = b;
return 0;
}
return -ENOENT;
}
int link_dhcp_enabled_by_bearer(Link *link, int family) {
Bearer *b;
int r;
assert(link);
assert(IN_SET(family, AF_INET, AF_INET6));
r = link_get_bearer(link, &b);
if (r < 0)
return r;
if (!b->connected)
return false;
if (!FLAGS_SET(b->ip_type, family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6))
return false;
return (family == AF_INET ? b->ip4_method : b->ip6_method) == MM_BEARER_IP_METHOD_DHCP;
}
static int bearer_address_handler(
sd_netlink *rtnl,
sd_netlink_message *m,
Request *req,
Link *link,
Address *address) {
int r;
assert(link);
r = address_configure_handler_internal(m, link, address);
if (r <= 0)
return r;
if (link->bearer_messages == 0) {
link->bearer_configured = true;
link_check_ready(link);
}
return 0;
}
static int link_request_bearer_address(
Link *link,
int family,
const union in_addr_union *addr,
unsigned prefixlen) {
_cleanup_(address_unrefp) Address *address = NULL;
Address *existing;
int r;
assert(link);
assert(IN_SET(family, AF_INET, AF_INET6));
assert(addr);
if (!in_addr_is_set(family, addr))
return 0;
r = address_new(&address);
if (r < 0)
return log_oom();
address->source = NETWORK_CONFIG_SOURCE_MODEM_MANAGER;
address->family = family;
address->in_addr = *addr;
address->prefixlen = prefixlen;
if (address_get(link, address, &existing) < 0) /* The address is new. */
link->bearer_configured = false;
else
address_unmark(existing);
r = link_request_address(link, address, &link->bearer_messages,
bearer_address_handler, /* ret = */ NULL);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to request address provided by bearer: %m");
return 0;
}
static int bearer_route_handler(
sd_netlink *rtnl,
sd_netlink_message *m,
Request *req,
Link *link,
Route *route) {
int r;
assert(link);
r = route_configure_handler_internal(m, req, route);
if (r <= 0)
return r;
if (link->bearer_messages == 0) {
link->bearer_configured = true;
link_check_ready(link);
}
return 0;
}
static int link_request_bearer_route(
Link *link,
int family,
const union in_addr_union *gw,
const union in_addr_union *prefsrc) {
_cleanup_(route_unrefp) Route *route = NULL;
Route *existing;
int r;
assert(link);
assert(IN_SET(family, AF_INET, AF_INET6));
assert(gw);
assert(prefsrc);
if (!in_addr_is_set(family, gw))
return 0;
if (link->network->mm_use_gateway == 0)
return 0;
r = route_new(&route);
if (r < 0)
return log_oom();
route->source = NETWORK_CONFIG_SOURCE_MODEM_MANAGER;
route->family = family;
route->nexthop.family = family;
route->nexthop.gw = *gw;
if (link->network->mm_route_metric_set) {
route->priority = link->network->mm_route_metric;
route->priority_set = true;
}
if (prefsrc)
route->prefsrc = *prefsrc;
if (route_get(link->manager, route, &existing) < 0) /* This is a new route. */
link->bearer_configured = false;
else
route_unmark(existing);
r = link_request_route(link, route, &link->bearer_messages, bearer_route_handler);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to request gateway provided by bearer: %m");
return 0;
}
static int link_apply_bearer_impl(Link *link, Bearer *b) {
Address *address;
Route *route;
int r, ret = 0;
assert(link);
if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
return 0;
/* First, mark bearer configs. */
SET_FOREACH(address, link->addresses) {
if (address->source != NETWORK_CONFIG_SOURCE_MODEM_MANAGER)
continue;
address_mark(address);
}
SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_MODEM_MANAGER)
continue;
route_mark(route);
}
if (b && FLAGS_SET(b->ip_type, ADDRESS_FAMILY_IPV4)) {
if (b->connected && b->ip4_method == MM_BEARER_IP_METHOD_STATIC) {
r = link_request_bearer_address(link, AF_INET, &b->ip4_address, b->ip4_prefixlen);
if (r < 0)
return r;
r = link_request_bearer_route(link, AF_INET, &b->ip4_gateway, &b->ip4_address);
if (r < 0)
return r;
}
if (b->connected && b->ip4_method == MM_BEARER_IP_METHOD_DHCP) {
if (!link_dhcp4_enabled(link))
log_link_notice(link, "The WWAN connection requested DHCPv4 client, but it is disabled.");
r = dhcp4_start(link);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to start DHCPv4 client: %m");
} else {
r = sd_dhcp_client_stop(link->dhcp_client);
if (r < 0)
ret = log_link_warning_errno(link, r, "Could not stop DHCPv4 client: %m");
}
}
if (b && FLAGS_SET(b->ip_type, ADDRESS_FAMILY_IPV6)) {
if (b->connected && b->ip6_method == MM_BEARER_IP_METHOD_STATIC) {
r = link_request_bearer_address(link, AF_INET6, &b->ip6_address, b->ip6_prefixlen);
if (r < 0)
return r;
r = link_request_bearer_route(link, AF_INET6, &b->ip6_gateway, NULL);
if (r < 0)
return r;
}
if (b->connected && b->ip6_method == MM_BEARER_IP_METHOD_DHCP) {
if (!link_ndisc_enabled(link) && !link_dhcp6_enabled(link))
log_link_notice(link,
"The WWAN connection requested IPv6 dynamic address configuration,"
"but both IPv6 Router Discovery and DHCPv6 client are disabled.");
r = ndisc_start(link);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to start IPv6 Router Discovery: %m");
r = dhcp6_start(link);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to start DHCPv6 client: %m");
} else {
r = sd_dhcp6_client_stop(link->dhcp6_client);
if (r < 0)
ret = log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m");
r = sd_ndisc_stop(link->ndisc);
if (r < 0)
ret = log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m");
}
}
/* Finally, remove all marked configs. */
SET_FOREACH(address, link->addresses) {
if (address->source != NETWORK_CONFIG_SOURCE_MODEM_MANAGER)
continue;
if (!address_is_marked(address))
continue;
r = address_remove(address, link);
if (r < 0)
ret = r;
}
SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_MODEM_MANAGER)
continue;
if (!route_is_marked(route))
continue;
r = route_remove(route, link->manager);
if (ret)
ret = r;
}
if (ret < 0)
return ret;
if (link->bearer_messages == 0)
link->bearer_configured = true;
if (!link->bearer_configured)
link_set_state(link, LINK_STATE_CONFIGURING);
link_check_ready(link);
return 0;
}
int link_apply_bearer(Link *link) {
Bearer *b = NULL;
int r;
assert(link);
(void) link_get_bearer(link, &b);
r = link_apply_bearer_impl(link, b);
if (r < 0)
link_enter_failed(link);
return r;
}
int bearer_update_link(Bearer *b) {
Link *link;
int r;
assert(b);
assert(b->modem);
assert(b->modem->manager);
if (!b->name)
return 0;
if (link_get_by_name(b->modem->manager, b->name, &link) < 0)
return 0;
r = link_apply_bearer_impl(link, b);
if (r < 0)
link_enter_failed(link);
/*
* Need to bring up the interface after the modem has connected.
* This is because ModemManger does the following while connecting:
* <msg> [1755871777.322239] [modem2] state changed (registered -> connecting)
* <dbg> [1755871777.325012] [modem2/bearer5] launching connection with QMI port (cdc-wdm0) and data port (wwan0) (multiplex none)
* <dbg> [1755871777.327665] [cdc-wdm0/qmi] bringing down data interface 'wwan0'
* <dbg> [1755871777.330108] [modem2/wwan0/net] interface index: 9
* <dbg> [1755871777.335265] [cdc-wdm0/qmi] deleting all links in data interface 'wwan0'
*/
r = link_request_to_bring_up_or_down(link, b->connected);
if (r < 0)
link_enter_failed(link);
return 0;
}
void bearer_drop(Bearer *b) {
assert(b);
b->connected = false;
b->apn = mfree(b->apn);
(void) bearer_update_link(b);
bearer_free(b);
}
int config_parse_mm_route_metric(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
network->mm_route_metric_set = false;
return 0;
}
r = safe_atou32(rvalue, &network->mm_route_metric);
if (r < 0)
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
network->mm_route_metric_set = true;
return 0;
}
+80
View File
@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "conf-parser-forward.h"
#include "in-addr-util.h"
#include "network-util.h"
#include "networkd-wwan-bus.h"
typedef struct Modem Modem;
typedef struct Bearer {
Modem *modem;
sd_bus_slot *slot_getall; /* for GetAll method */
char *path; /* DBus path e.g /org/freedesktop/ModemManager/Bearer/0 */
char *name; /* Interface property, e.g. wwan0 */
char *apn; /* "apn" field in Properties */
AddressFamily ip_type; /* "ip-type" field in Properties */
/* Ip4Config or IP6Config property */
MMBearerIpMethod ip4_method;
MMBearerIpMethod ip6_method;
unsigned ip4_prefixlen;
unsigned ip6_prefixlen;
union in_addr_union ip4_address;
union in_addr_union ip6_address;
union in_addr_union ip4_gateway;
union in_addr_union ip6_gateway;
struct in_addr_full **dns;
size_t n_dns;
uint32_t ip4_mtu;
uint32_t ip6_mtu;
bool connected; /* Connected property */
} Bearer;
typedef struct Modem {
Manager *manager;
sd_bus_slot *slot_propertieschanged;
sd_bus_slot *slot_statechanged;
sd_bus_slot *slot_connect;
char *path; /* DBus path e.g /org/freedesktop/ModemManager/Modem/0 */
char *manufacturer; /* The "Manufacturer" property */
char *model; /* The "Model" property */
char *port_name; /* MM_MODEM_PORT_TYPE_NET of Ports property */
Hashmap *bearers_by_path;
Hashmap *bearers_by_name;
MMModemState state;
MMModemStateFailedReason state_fail_reason;
ModemReconnectState reconnect_state;
} Modem;
int bearer_new(Modem *modem, const char *path, Bearer **ret);
Bearer* bearer_free(Bearer *b);
DEFINE_TRIVIAL_CLEANUP_FUNC(Bearer*, bearer_free);
int bearer_set_name(Bearer *b, const char *name);
int bearer_get_by_path(Manager *manager, const char *path, Modem **ret_modem, Bearer **ret_bearer);
int link_get_bearer(Link *link, Bearer **ret);
int link_dhcp_enabled_by_bearer(Link *link, int family);
int link_apply_bearer(Link *link);
int bearer_update_link(Bearer *b);
void bearer_drop(Bearer *b);
int modem_new(Manager *m, const char *path, Modem **ret);
Modem* modem_free(Modem *modem);
DEFINE_TRIVIAL_CLEANUP_FUNC(Modem*, modem_free);
int modem_get_by_path(Manager *m, const char *path, Modem **ret);
int link_get_modem(Link *link, Modem **ret);
CONFIG_PARSER_PROTOTYPE(config_parse_mm_route_metric);
+1
View File
@@ -573,3 +573,4 @@ loader.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="rebo
loader.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="secure-boot-enroll-action"]/listitem/variablelist/varlistentry[term="reboot"]
loader.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="secure-boot-enroll-action"]/listitem/variablelist/varlistentry[term="shutdown"]
varlinkctl.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--system"]
systemd.network.xml ./refsect1[title="[ModemManager] Section Options"]/variablelist/varlistentry[term="SimpleConnectProperties="]