Snap refresh over metered connections

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 https://github.com/NetworkManager/NetworkManager/blob/5f1c1be4624fd5b167592f72b82beae1a9c38a3d/src/dhcp/nm-dhcp-utils.c#L577 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

6 Likes

I think the overall proposal looks good but I have some suggestions:

  • we should investigate what is wrong with modem-manager/network-manager
  • we should design how this changes snap requests like install, refresh, etc when performed from command line
  • we should probably also tweak snap list/info, to show that update is available but ignored

What do you think?

2 Likes

For what it’s worth, we actually have a similar integration in place for the dnf-makecache timer service in Fedora. When NetworkManager tells DNF that it’s a metered connection, automated refreshes of metadata are disabled automatically to prevent unnecessary consumption of limited bandwidth.

I’m actually very surprised we don’t do this now, since automatic refreshes have this consequence which is painful for people with limited bandwidth.

3 Likes

I’ve put this feature in my queue. It’s a nice gain while not being too work intensive.

1 Like

I have proposed a PR with the change:
https://github.com/snapcore/snapd/pull/5091

3 Likes

I was not using snap at all on my desktop because of these auto refreshes, since I own a 3G connection with limited data plan. But on Ubuntu 18.04, I’m re-evaluating snaps and it’s frustrating for us not to have a global switch for not updating…

I think this is a nice proposal which will work for people with limited data plan and am waiting to see it implemented and available to us.

2 Likes

how would you normally go about updating your software? Let’s say you have a number of big snaps installed that have updates, many of them security fixes, needing say 1GB of data to complete. What would you do?

I postpone my updates until I get chances to connect my pc with unlimited or bigger data plan. Sometimes mobile broadband providers offer bonus data which I use to update most important packages. This is how I get via apt etc.

I haven’t used snaps yet, So I can’t exactly tell how would I do. If I am given the choice to update selectively, I’ll update the most important ones.

1 Like

that’s exactly the information I was looking for, thank you.

Basically, the current proposal should work, especially if we make the default be to hold the refresh on known-metered connections.

Currently you can only hold refreshes for 60 days before it’ll give up and try anyway, but it sounds like that should be alright.

2 Likes

Yeah, I admit 60 day hold of refreshes is a good option. But it may not work for us. I think this proposal is a balanced solution between auto refresh vs global switch to disable auto update. Especially for us, who want the global switch for restricted data policy.

Thank you. I’m waiting for this to be available to us soon.

1 Like

Go to cafe with good WiFi.

This has now landed in master and should be available in edge channel soon. To use the feature, one needs to set:

$ snap set system refresh.metered=hold

To revert (i.e. disable holding refreshes while on metered connections) do:

$ snap set system refresh.metered=null    # or just refresh.metered=

The maximum time of holding refreshes while on metered connection is 60 days, aftere which a refresh will be attempted anyway.

Network Manager is the only supported source on information about the default connection being metered at the moment.

3 Likes

Amazing thank you! :blush:

How does this stack with the refresh timer? Is a refresh attempted immediately after the 60 days regardless of what the timer is set to or will it refresh when the timer has set refreshes? What’s the cooldown period after the 60 days? If it’s set to null after 60 days and then set back to hold, are refreshes held for another 60 days? What happens if I changed it to null and immediately back to hold after 59 days? Does that reset the countdown? Or is this info not public (in which case I guess someone could deduce the answer by checking the code?) :slight_smile:

It’s checked against the time of last refresh. Basically, if you are on metered and last refresh was more than 60 days ago, the refresh can proceed.

1 Like

Going back to the GNOME 3.28 feature, does toggling there also automatically toggle the snapd setting or is that a bug that I need to file (against snapd or GNOME)? :slight_smile:

1 Like

We don’t look at any gnome settings so I suspect some integration is in order

1 Like

Likely not, as that seems to be a NetworkManager frontend for the aforementioned metered connection manual override.

I believe snapd should enable the metered connection detection by default in the future without one to manually set the feature.

1 Like

Flatpak has their automatic updates automatically disabled on metered connections (4th heading), hopefully snappy can catch up (sorry that I have not been able to help) :slight_smile:

3 Likes

Testing steps

Basic run through

Enable holding refreshes when on a metered connection:

$ snap set system refresh.metered=hold

Check if your connection is considered to be metered by NetworkManager. First determine the name of active connection:

$ nmcli c show --active
NAME     UUID                                  TYPE  DEVICE
G6_3577  31517f2b-25e7-4189-ab95-6d01f1482a67  wifi  wlp3s0

The host is connected to network G6_3577 which a hotspot on my phone. Check the human readable status (can be yes, no, unknown):

$ nmcli c show 'G6_3577' | grep connection.metered
connection.metered:                     unknown

If the status is unknown, try to determine the exact status reported by NetworkManager:

$ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 3

NetworkManager reported status code 3. Refer to the list of status codes reported by NetworkManager:

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

Status code 3 is NM_METERED_GUESS_YES. Only status codes 1 (NM_METERED_YES) and 3 (NM_METERED_GUESS_YES) will make snapd hold refreshes.

Leave it at that and wait for refresh to kick in. You should observe the following in snapd logs:

Sep 07 13:13:10 corsair snapd[8854]: autorefresh.go:186: DEBUG: Auto refresh disabled on metered connections
Sep 07 13:18:10 corsair snapd[8854]: autorefresh.go:255: DEBUG: Next refresh scheduled for 2018-09-07 13:18:10.159430386 +0200 CEST m=+1200.109631500.
Sep 07 13:18:10 corsair snapd[8854]: autorefresh.go:186: DEBUG: Auto refresh disabled on metered connections
...

My connection is not metered but should be

You can make NetworkManger mark any connection as metered using either command line or the graphical tools (eg. GNOME’s network settings).

Assume I have a network connection named wireless 5.8. NetworkManager did not correctly find out that the connection is metered.

$ nmcli c show --active
NAME            UUID                                  TYPE  DEVICE 
wireless 5.8    227b1939-f7c6-4769-8ac0-7393936eb1d3  wifi  wlp3s0 
$ nmcli c show 'wireless 5.8' |grep connection.metered
connection.metered:                     unknown
$ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 4

Reported status code is 4 (NM_METERED_GUESS_NO).

The CLI way

Modify connection properties and mark it as metered:

$ nmcli c modify 'wireless 5.8' connection.metered true

Double check the reported status:

$ nmcli c show 'wireless 5.8' |grep connection.metered       
connection.metered:                     yes
$ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 1

Reported status is 1 (NM_METERED_YES).

The GUI way

Head to GNOME network settings:

https://i.imgur.com/AimSeWM.png

Select your currently connected network settings:

https://i.imgur.com/nvcEany.png

And tick Restrict background data usage, then Apply.

Double check the reported status:

$ nmcli c show 'wireless 5.8' |grep connection.metered       
connection.metered:                     yes
$ busctl --system get-property org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager org.freedesktop.NetworkManager Metered
u 1

Reported status is 1 (NM_METERED_YES).

2 Likes

Can snap set system refresh.metered=hold be the default in future releases of snapd please (assuming this works well enough) so that updates are held back by default on metered connections rather than allowed by default?

1 Like