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
+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);