Rationale
In the lengthy topic about disabling snap refresh, a user bought up this:
NetworkManager is widely used across all major Linux distributions, I thought it might be useful to at least test the waters and so some digging and come up with a proposal. Limited data plans are unfortunately a thing. A user with several GB data plan, tethering wifi from her phone, may not want snaps to get refreshed in such conditions. Similarly, a device with a fallback link over 3G may similarly, inhibit snap refresh, until the main link is up.
We already have a feature to hold refreshes for some time, see delaying refreshes and registartion topic for details. The functionality however, requires manual intervention (issue the command) and the time the refresh are to be held has to be specified upfront (I may be nitpicking here a bit). The support for metered connections could be more to what GNOME Software and PackageKit does, i.e. it stops the updates and is automatic.
The meterd connections feature was introduced back in 2015, in NM 1.0.6: https://blogs.gnome.org/lkundrak/2015/08/27/networkmanager-1-0-6-brings-metered-connections-api-and-more/. For the record Ubuntu 16.04 was shipped with NM 1.2.2.
NetworkManager
DBus
NM exposes a Metered
property of each device. It is described is of u
type and is described as:
Whether the amount of traffic flowing through the device is subject to limitations, for example set by service providers
The value is a NMMetered
enum:
NM_METERED_UNKNOWN = 0 The metered status is unknown
NM_METERED_YES = 1 Metered, the value was statically set
NM_METERED_NO = 2 Not metered, the value was statically set
NM_METERED_GUESS_YES = 3 Metered, the value was guessed
NM_METERED_GUESS_NO = 4 Not metered, the value was guessed
The information is also exposed on the main org.freedesktop.NetworkManager
object, and takes the value of current default connection.
Internals
The Metered property can be set either explicitly by the user or automatically.
One automatic path I found in the code is looking at vendor specific options:
See NetworkManager/src/dhcp/nm-dhcp-utils.c at 5f1c1be4624fd5b167592f72b82beae1a9c38a3d · NetworkManager/NetworkManager · GitHub looking for ANDROID_METERED
. Some more details: https://www.lorier.net/docs/android-metered.html I could not find more useful documentation on the setting. Maybe someone who knows AOSP in more detail could help.
Another one looks at the device: https://github.com/NetworkManager/NetworkManager/blob/master/src/devices/nm-device.c#L12486 Specifically Bluetooth PAN/DUN setup and falls back to checking whether the device is a modem.
Practice
NetworkManager exposes a metered property on each connection. On example host connected over wifi:
maciek@galeon:~ nmcli c show --active
NAME UUID TYPE DEVICE
wireless 5.8 dc72a54d-bd51-4f40-99d5-93fe9ef15497 wifi wlp30s0
maciek@galeon:~ nmcli c show 'wireless 5.8' |grep connection.metered
connection.metered: unknown
maciek@galeon:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 4
The status is clearly unknown.
Connecting to a 4G modem over WIFI:
maciek@corsair:~ nmcli c show --active
NAME UUID TYPE DEVICE
4G-Gateway-74F5 1 87c76982-4901-4a8c-8ed0-806c6b201fba wifi wlp3s0
maciek@corsair:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 4
maciek@corsair:~ nmcli c show 4G-Gateway-74F5 |grep metered
connection.metered: unknown
The detection may be a bit flaky. The modem is not sending proper data in IP vendor specific options:
maciek@corsair:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager/DHCP4Config/6 \
org.freedesktop.NetworkManager.DHCP4Config Options
a{sv} 23 "ip_address" s "192.168.32.173" "domain_name_servers" s "192.168.32.1"
"subnet_mask" s "255.255.255.0" "requested_static_routes" s "1"
"requested_nis_servers" s "1" "requested_domain_search" s "1"
"requested_ntp_servers" s "1" "requested_subnet_mask" s "1"
"requested_interface_mtu" s "1" "requested_domain_name" s "1"
"host_name" s "corsair" "expiry" s "1523947104" "routers" s "192.168.32.1"
"requested_broadcast_address" s "1"
"requested_dhcp_server_identifier" s "1"
"requested_ms_classless_static_routes" s "1"
"requested_domain_name_servers" s "1"
"requested_wpad" s "1" "requested_nis_domain" s "1"
"requested_rfc3442_classless_static_routes" s "1"
"requested_time_offset" s "1"
"requested_routers" s "1" "requested_host_name" s "1"
The connection can be explicitly made metered:
maciek@corsair:~ nmcli c modify 4G-Gateway-74F5 connection.metered true
maciek@corsair:~ nmcli c show 4G-Gateway-74F5 |grep metered
connection.metered: yes
maciek@corsair:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 4
Still showing no, perhaps there is a bug in NM. Restarting NM helps:
maciek@corsair:~ systemctl restart NetworkManager
maciek@corsair:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 1
That’s a NM_METERED_YES.
WiFi tethering from the mobile (Lineage OS 14.1, Android 7.1.2):
maciek@corsair:~ nmcli c show --active
NAME UUID TYPE DEVICE
ziemniak 5ad7ff52-8a03-4a0f-98e6-1ebd8cdeaf49 wifi wlp3s0
maciek@corsair:~ nmcli c show ziemniak |grep metered
connection.metered: unknown
maciek@corsair:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 3
That’s a NM_METERED_GUESS_YES, However I was not able to determine why. The DHCP options also show nothing in particular:
maciek@corsair:~ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager/DHCP4Config/9 \
org.freedesktop.NetworkManager.DHCP4Config Options
a{sv} 23 "ip_address" s "192.168.43.136" "domain_name_servers" s "192.168.43.1"
"subnet_mask" s "255.255.255.0" "requested_static_routes" s "1"
"requested_nis_servers" s "1" "requested_domain_search" s "1"
"requested_ntp_servers" s "1" "requested_subnet_mask" s "1"
"requested_interface_mtu" s "1" "requested_domain_name" s "1"
"host_name" s "corsair" "expiry" s "1523864550" "routers" s "192.168.43.1"
"requested_broadcast_address" s "1"
"requested_dhcp_server_identifier" s "1" "requested_ms_classless_static_routes" s "1"
"requested_domain_name_servers" s "1"
"requested_wpad" s "1" "requested_nis_domain" s "1"
"requested_rfc3442_classless_static_routes" s "1" "requested_time_offset" s "1"
"requested_routers" s "1" "requested_host_name" s "1"
Proposal
- Add a new setting for
core
,refresh.hold-on-metered
, default to false - Gadget snaps can provide default settings for core and default to true
- In absence of NM (or other source of knowledge about the connection being metered), the setting is ineffective
- Make snapd look at the state of
Metered
connection iff NetworkManager is available in the system
cc @niemeyer