wifi: guess metered flag based on Network Cost information element

Network Cost [1] is a vendor-specific information element defined by
Microsoft and used to advertise the cost of Wi-Fi networks to clients.

We can use it together with the ANDROID_METERED mechanism to
automatically set the metered flag on the device.

[1] https://docs.microsoft.com/en-us/windows-hardware/drivers/mobilebroadband/network-cost-information-element

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/issues/200
This commit is contained in:
Beniamino Galvani
2019-09-10 10:32:55 +02:00
parent 44193d3def
commit 5307b1ed73
5 changed files with 58 additions and 21 deletions

View File

@@ -14091,6 +14091,11 @@ nm_device_update_metered (NMDevice *self)
} }
} }
if ( value == NM_METERED_INVALID
&& NM_DEVICE_GET_CLASS (self)->get_guessed_metered
&& NM_DEVICE_GET_CLASS (self)->get_guessed_metered (self))
value = NM_METERED_GUESS_YES;
/* Try to guess a value using the metered flag in IP configuration */ /* Try to guess a value using the metered flag in IP configuration */
if (value == NM_METERED_INVALID) { if (value == NM_METERED_INVALID) {
if ( priv->ip_config_4 if ( priv->ip_config_4

View File

@@ -435,6 +435,8 @@ typedef struct _NMDeviceClass {
guint32 (* get_dhcp_timeout) (NMDevice *self, guint32 (* get_dhcp_timeout) (NMDevice *self,
int addr_family); int addr_family);
gboolean (* get_guessed_metered) (NMDevice *self);
/* Controls, whether to call act_stage2_config() callback also for assuming /* Controls, whether to call act_stage2_config() callback also for assuming
* a device or for external activations. In this case, act_stage2_config() must * a device or for external activations. In this case, act_stage2_config() must
* take care not to touch the device's configuration. */ * take care not to touch the device's configuration. */

View File

@@ -3165,6 +3165,15 @@ set_enabled (NMDevice *device, gboolean enabled)
} }
} }
static gboolean
get_guessed_metered (NMDevice *device)
{
NMDeviceWifi *self = NM_DEVICE_WIFI (device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
return priv->current_ap && nm_wifi_ap_get_metered (priv->current_ap);
}
static gboolean static gboolean
can_reapply_change (NMDevice *device, can_reapply_change (NMDevice *device,
const char *setting_name, const char *setting_name,
@@ -3375,6 +3384,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
device_class->check_connection_available = check_connection_available; device_class->check_connection_available = check_connection_available;
device_class->complete_connection = complete_connection; device_class->complete_connection = complete_connection;
device_class->get_enabled = get_enabled; device_class->get_enabled = get_enabled;
device_class->get_guessed_metered = get_guessed_metered;
device_class->set_enabled = set_enabled; device_class->set_enabled = set_enabled;
device_class->act_stage1_prepare = act_stage1_prepare; device_class->act_stage1_prepare = act_stage1_prepare;

View File

@@ -53,10 +53,12 @@ struct _NMWifiAPPrivate {
NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */ NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */
NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */ NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */
bool metered:1;
/* Non-scanned attributes */ /* Non-scanned attributes */
bool fake:1; /* Whether or not the AP is from a scan */ bool fake:1; /* Whether or not the AP is from a scan */
bool hotspot:1; /* Whether the AP is a local device's hotspot network */ bool hotspot:1; /* Whether the AP is a local device's hotspot network */
gint32 last_seen; /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */ gint32 last_seen; /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
}; };
typedef struct _NMWifiAPPrivate NMWifiAPPrivate; typedef struct _NMWifiAPPrivate NMWifiAPPrivate;
@@ -392,6 +394,12 @@ nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen)
return FALSE; return FALSE;
} }
gboolean
nm_wifi_ap_get_metered (const NMWifiAP *self)
{
return NM_WIFI_AP_GET_PRIVATE (self)->metered;
}
/*****************************************************************************/ /*****************************************************************************/
static NM80211ApSecurityFlags static NM80211ApSecurityFlags
@@ -717,44 +725,50 @@ get_max_rate_vht (const guint8 *bytes, guint len, guint32 *out_maxrate)
/* Management Frame Information Element IDs, ieee80211_eid */ /* Management Frame Information Element IDs, ieee80211_eid */
#define WLAN_EID_HT_CAPABILITY 45 #define WLAN_EID_HT_CAPABILITY 45
#define WLAN_EID_VHT_CAPABILITY 191 #define WLAN_EID_VHT_CAPABILITY 191
#define WLAN_EID_VENDOR_SPECIFIC 221
static guint32 static void
get_max_rate (const guint8 *bytes, gsize len) parse_ies (const guint8 *bytes, gsize len, guint32 *out_max_rate, gboolean *out_metered)
{ {
guint8 id, elem_len; guint8 id, elem_len;
guint32 max_rate = 0; guint32 m;
*out_max_rate = 0;
*out_metered = FALSE;
while (len) { while (len) {
guint32 m;
if (len < 2) if (len < 2)
return 0; break;
id = *bytes++; id = *bytes++;
elem_len = *bytes++; elem_len = *bytes++;
len -= 2; len -= 2;
if (elem_len > len) if (elem_len > len)
return 0; break;
switch (id) { switch (id) {
case WLAN_EID_HT_CAPABILITY: case WLAN_EID_HT_CAPABILITY:
if (!get_max_rate_ht (bytes, elem_len, &m)) if (get_max_rate_ht (bytes, elem_len, &m))
return 0; *out_max_rate = NM_MAX (*out_max_rate, m);
max_rate = NM_MAX (max_rate, m);
break; break;
case WLAN_EID_VHT_CAPABILITY: case WLAN_EID_VHT_CAPABILITY:
if (!get_max_rate_vht (bytes, elem_len, &m)) if (get_max_rate_vht (bytes, elem_len, &m))
return 0; *out_max_rate = NM_MAX (*out_max_rate, m);
max_rate = NM_MAX (max_rate, m); break;
case WLAN_EID_VENDOR_SPECIFIC:
if ( len == 8
&& bytes[0] == 0x00 /* OUI: Microsoft */
&& bytes[1] == 0x50
&& bytes[2] == 0xf2
&& bytes[3] == 0x11) /* OUI type: Network cost */
*out_metered = (bytes[7] > 1); /* Cost level > 1 */
break; break;
} }
len -= elem_len; len -= elem_len;
bytes += elem_len; bytes += elem_len;
} }
return max_rate;
} }
/*****************************************************************************/ /*****************************************************************************/
@@ -774,7 +788,8 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
gint16 i16; gint16 i16;
guint16 u16; guint16 u16;
gboolean changed = FALSE; gboolean changed = FALSE;
guint32 max_rate; gboolean metered;
guint32 max_rate, rate;
g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE); g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
g_return_val_if_fail (properties, FALSE); g_return_val_if_fail (properties, FALSE);
@@ -855,9 +870,12 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING); v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
if (v) { if (v) {
bytes = g_variant_get_fixed_array (v, &len, 1); bytes = g_variant_get_fixed_array (v, &len, 1);
max_rate = NM_MAX (max_rate, get_max_rate (bytes, len)); parse_ies (bytes, len, &rate, &metered);
max_rate = NM_MAX (max_rate, rate);
g_variant_unref (v); g_variant_unref (v);
priv->metered = metered;
} }
if (max_rate) if (max_rate)
changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000); changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000);
@@ -988,7 +1006,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
export_path = "/"; export_path = "/";
g_snprintf (str_buf, buf_len, g_snprintf (str_buf, buf_len,
"%17s %-35s [ %c %3u %3u%% %c W:%04X R:%04X ] %3us sup:%s [nm:%s]", "%17s %-35s [ %c %3u %3u%% %c%c W:%04X R:%04X ] %3us sup:%s [nm:%s]",
priv->address ?: "(none)", priv->address ?: "(none)",
(ssid_to_free = _nm_utils_ssid_to_string (priv->ssid)), (ssid_to_free = _nm_utils_ssid_to_string (priv->ssid)),
(priv->mode == NM_802_11_MODE_ADHOC (priv->mode == NM_802_11_MODE_ADHOC
@@ -1003,6 +1021,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
chan, chan,
priv->strength, priv->strength,
priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_', priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_',
priv->metered ? 'M' : '_',
priv->wpa_flags & 0xFFFF, priv->wpa_flags & 0xFFFF,
priv->rsn_flags & 0xFFFF, priv->rsn_flags & 0xFFFF,
priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1, priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,

View File

@@ -81,6 +81,7 @@ gboolean nm_wifi_ap_get_fake (const NMWifiAP *ap);
gboolean nm_wifi_ap_set_fake (NMWifiAP *ap, gboolean nm_wifi_ap_set_fake (NMWifiAP *ap,
gboolean fake); gboolean fake);
NM80211ApFlags nm_wifi_ap_get_flags (const NMWifiAP *self); NM80211ApFlags nm_wifi_ap_get_flags (const NMWifiAP *self);
gboolean nm_wifi_ap_get_metered (const NMWifiAP *self);
const char *nm_wifi_ap_to_string (const NMWifiAP *self, const char *nm_wifi_ap_to_string (const NMWifiAP *self,
char *str_buf, char *str_buf,