diff --git a/clients/cli/common.c b/clients/cli/common.c
index 97d0bb940e..35676d0f2b 100644
--- a/clients/cli/common.c
+++ b/clients/cli/common.c
@@ -521,6 +521,23 @@ nmc_device_state_to_string (NMDeviceState state)
}
}
+const char *
+nmc_device_metered_to_string (NMMetered value)
+{
+ switch (value) {
+ case NM_METERED_YES:
+ return _("yes");
+ case NM_METERED_NO:
+ return _("no");
+ case NM_METERED_GUESS_YES:
+ return _("yes (guessed)");
+ case NM_METERED_GUESS_NO:
+ return _("no (guessed)");
+ default:
+ return _("unknown");
+ }
+}
+
const char *
nmc_device_reason_to_string (NMDeviceStateReason reason)
{
diff --git a/clients/cli/common.h b/clients/cli/common.h
index 8bb043f4d7..bb3f44e851 100644
--- a/clients/cli/common.h
+++ b/clients/cli/common.h
@@ -35,6 +35,7 @@ NMIPRoute *nmc_parse_and_build_route (int family, const char *first, const char
const char * nmc_device_state_to_string (NMDeviceState state);
const char * nmc_device_reason_to_string (NMDeviceStateReason reason);
+const char * nmc_device_metered_to_string (NMMetered value);
char **
nmc_vlan_parse_priority_maps (const char *priority_map,
diff --git a/clients/cli/devices.c b/clients/cli/devices.c
index 979c590635..41564a5ab7 100644
--- a/clients/cli/devices.c
+++ b/clients/cli/devices.c
@@ -78,11 +78,12 @@ static NmcOutputField nmc_fields_dev_show_general[] = {
{"CONNECTION", N_("CONNECTION"), 20}, /* 20 */
{"CON-UUID", N_("CON-UUID"), 38}, /* 21 */
{"CON-PATH", N_("CON-PATH"), 51}, /* 22 */
+ {"METERED", N_("METERED"), 10}, /* 23 */
{NULL, NULL, 0}
};
#define NMC_FIELDS_DEV_SHOW_GENERAL_ALL "NAME,DEVICE,TYPE,NM-TYPE,VENDOR,PRODUCT,DRIVER,DRIVER-VERSION,FIRMWARE-VERSION,HWADDR,MTU,"\
"STATE,REASON,UDI,IP-IFACE,IS-SOFTWARE,NM-MANAGED,AUTOCONNECT,FIRMWARE-MISSING,PHYS-PORT-ID,"\
- "CONNECTION,CON-UUID,CON-PATH"
+ "CONNECTION,CON-UUID,CON-PATH,METERED"
#define NMC_FIELDS_DEV_SHOW_GENERAL_COMMON "NAME,DEVICE,TYPE,VENDOR,PRODUCT,DRIVER,HWADDR,STATE"
/* Available fields for 'device show' - CONNECTIONS part */
@@ -858,6 +859,8 @@ show_device_info (NMDevice *device, NmCli *nmc)
set_val_strc (arr, 20, get_active_connection_id (device));
set_val_strc (arr, 21, acon ? nm_active_connection_get_uuid (acon) : NULL);
set_val_strc (arr, 22, acon ? nm_object_get_path (NM_OBJECT (acon)) : NULL);
+ set_val_strc (arr, 23, nmc_device_metered_to_string (nm_device_get_metered (device)));
+
g_ptr_array_add (nmc->output_data, arr);
print_data (nmc); /* Print all data */
diff --git a/clients/cli/settings.c b/clients/cli/settings.c
index 66759dfc56..6ede34c42c 100644
--- a/clients/cli/settings.c
+++ b/clients/cli/settings.c
@@ -60,6 +60,7 @@ NmcOutputField nmc_fields_setting_connection[] = {
SETTING_FIELD (NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, 13), /* 13 */
SETTING_FIELD (NM_SETTING_CONNECTION_SECONDARIES, 40), /* 14 */
SETTING_FIELD (NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, 30), /* 15 */
+ SETTING_FIELD (NM_SETTING_CONNECTION_METERED, 10), /* 16 */
{NULL, NULL, 0, NULL, FALSE, FALSE, 0}
};
#define NMC_FIELDS_SETTING_CONNECTION_ALL "name"","\
@@ -77,7 +78,8 @@ NmcOutputField nmc_fields_setting_connection[] = {
NM_SETTING_CONNECTION_SLAVE_TYPE","\
NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES","\
NM_SETTING_CONNECTION_SECONDARIES","\
- NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT
+ NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT","\
+ NM_SETTING_CONNECTION_METERED
#define NMC_FIELDS_SETTING_CONNECTION_COMMON NMC_FIELDS_SETTING_CONNECTION_ALL
/* Available fields for NM_SETTING_WIRED_SETTING_NAME */
@@ -2744,6 +2746,51 @@ nmc_property_connection_describe_secondaries (NMSetting *setting, const char *pr
"Example: private-openvpn, fe6ba5d8-c2fc-4aae-b2e3-97efddd8d9a7\n");
}
+/* 'metered' */
+static char *
+nmc_property_connection_get_metered (NMSetting *setting, NmcPropertyGetType get_type)
+{
+ NMSettingConnection *s_conn = NM_SETTING_CONNECTION (setting);
+
+ switch (nm_setting_connection_get_metered (s_conn)) {
+ case NM_METERED_YES:
+ return g_strdup (_("yes"));
+ case NM_METERED_NO:
+ return g_strdup (_("no"));
+ case NM_METERED_UNKNOWN:
+ default:
+ return g_strdup (_("unknown"));
+ }
+}
+
+static gboolean
+nmc_property_connection_set_metered (NMSetting *setting, const char *prop,
+ const char *val, GError **error)
+{
+ NMMetered metered;
+ NMCTriStateValue ts_val;
+
+ if (!nmc_string_to_tristate (val, &ts_val, error))
+ return FALSE;
+
+ switch (ts_val) {
+ case NMC_TRI_STATE_YES:
+ metered = NM_METERED_YES;
+ break;
+ case NMC_TRI_STATE_NO:
+ metered = NM_METERED_NO;
+ break;
+ case NMC_TRI_STATE_UNKNOWN:
+ metered = NM_METERED_UNKNOWN;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ g_object_set (setting, prop, metered, NULL);
+ return TRUE;
+}
+
/* --- NM_SETTING_802_1X_SETTING_NAME property setter functions --- */
#define DEFINE_SETTER_STR_LIST(def_func, set_func) \
static gboolean \
@@ -5324,6 +5371,13 @@ nmc_properties_init (void)
NULL,
NULL,
NULL);
+ nmc_add_prop_funcs (GLUE (CONNECTION, METERED),
+ nmc_property_connection_get_metered,
+ nmc_property_connection_set_metered,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
/* Add editable properties for NM_SETTING_DCB_SETTING_NAME */
nmc_add_prop_funcs (GLUE (DCB, APP_FCOE_FLAGS),
@@ -6684,6 +6738,7 @@ setting_connection_details (NMSetting *setting, NmCli *nmc, const char *one_pro
set_val_str (arr, 13, nmc_property_connection_get_autoconnect_slaves (setting, NMC_PROPERTY_GET_PRETTY));
set_val_str (arr, 14, nmc_property_connection_get_secondaries (setting, NMC_PROPERTY_GET_PRETTY));
set_val_str (arr, 15, nmc_property_connection_get_gateway_ping_timeout (setting, NMC_PROPERTY_GET_PRETTY));
+ set_val_str (arr, 16, nmc_property_connection_get_metered (setting, NMC_PROPERTY_GET_PRETTY));
g_ptr_array_add (nmc->output_data, arr);
print_data (nmc); /* Print all data */
diff --git a/clients/cli/utils.c b/clients/cli/utils.c
index dfd002edce..3cc836d83e 100644
--- a/clients/cli/utils.c
+++ b/clients/cli/utils.c
@@ -407,7 +407,10 @@ nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error)
if (g_strcmp0 (str, "o") == 0) {
g_set_error (error, 1, 0,
- _("'%s' is ambiguous (on x off)"), str);
+ /* Translators: the first %s is the partial value entered by
+ * the user, the second %s a list of compatible values.
+ */
+ _("'%s' is ambiguous (%s)"), str, "on x off");
return FALSE;
}
@@ -424,6 +427,39 @@ nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error)
return TRUE;
}
+gboolean
+nmc_string_to_tristate (const char *str, NMCTriStateValue *val, GError **error)
+{
+ const char *s_true[] = { "true", "yes", "on", NULL };
+ const char *s_false[] = { "false", "no", "off", NULL };
+ const char *s_unknown[] = { "unknown", NULL };
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (g_strcmp0 (str, "o") == 0) {
+ g_set_error (error, 1, 0,
+ /* Translators: the first %s is the partial value entered by
+ * the user, the second %s a list of compatible values.
+ */
+ _("'%s' is ambiguous (%s)"), str, "on x off");
+ return FALSE;
+ }
+
+ if (nmc_string_is_valid (str, s_true, NULL))
+ *val = NMC_TRI_STATE_YES;
+ else if (nmc_string_is_valid (str, s_false, NULL))
+ *val = NMC_TRI_STATE_NO;
+ else if (nmc_string_is_valid (str, s_unknown, NULL))
+ *val = NMC_TRI_STATE_UNKNOWN;
+ else {
+ g_set_error (error, 1, 0,
+ _("'%s' is not valid; use [%s], [%s] or [%s]"),
+ str, "true, yes, on", "false, no, off", "unknown");
+ return FALSE;
+ }
+ return TRUE;
+}
+
/*
* Ask user for input and return the string.
* The caller is responsible for freeing the returned string.
diff --git a/clients/cli/utils.h b/clients/cli/utils.h
index 77e697af66..ba649f8fae 100644
--- a/clients/cli/utils.h
+++ b/clients/cli/utils.h
@@ -32,6 +32,12 @@ typedef struct {
gboolean found;
} nmc_arg_t;
+typedef enum {
+ NMC_TRI_STATE_NO,
+ NMC_TRI_STATE_YES,
+ NMC_TRI_STATE_UNKNOWN,
+} NMCTriStateValue;
+
/* === Functions === */
int matches (const char *cmd, const char *pattern);
int next_arg (int *argc, char ***argv);
@@ -62,6 +68,7 @@ gboolean nmc_string_to_uint (const char *str,
unsigned long int max,
unsigned long int *value);
gboolean nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error);
+gboolean nmc_string_to_tristate (const char *str, NMCTriStateValue *val, GError **error);
char *nmc_ip4_address_as_string (guint32 ip, GError **error);
char *nmc_ip6_address_as_string (const struct in6_addr *ip, GError **error);
void nmc_terminal_erase_line (void);
diff --git a/introspection/nm-device.xml b/introspection/nm-device.xml
index e405a384ba..b2e3827e2a 100644
--- a/introspection/nm-device.xml
+++ b/introspection/nm-device.xml
@@ -139,6 +139,12 @@
The device MTU (maximum transmission unit).
+
+
+ Whether the amount of traffic flowing through the device is
+ subject to limitations, for example set by service providers.
+
+
@@ -654,6 +660,34 @@
+
+
+
+ The device metered status is unknown.
+
+
+
+
+ The device is metered and the value was statically set.
+
+
+
+
+ The device is not metered and the value was statically set.
+
+
+
+
+ The device is metered and the value was guessed.
+
+
+
+
+ The device is not metered and the value was guessed.
+
+
+
+
diff --git a/introspection/nm-manager.xml b/introspection/nm-manager.xml
index a9bb6dcf14..f0d04438d5 100644
--- a/introspection/nm-manager.xml
+++ b/introspection/nm-manager.xml
@@ -335,6 +335,14 @@
+
+
+ Wheter the connectivity is metered. This is equivalent to the
+ metered property of the device associated with the primary
+ connection.
+
+
+
The object path of an active connection that is currently
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index ccc3b3a9e9..8c5cd3c4c1 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -403,7 +403,6 @@ typedef enum {
NM_DEVICE_STATE_FAILED = 120
} NMDeviceState;
-
/**
* NMDeviceStateReason:
* @NM_DEVICE_STATE_REASON_NONE: No reason given
@@ -540,6 +539,26 @@ typedef enum {
NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED = 62,
} NMDeviceStateReason;
+/**
+ * NMMetered:
+ * @NM_METERED_UNKNOWN: The metered status is unknown
+ * @NM_METERED_YES: Metered, the value was statically set
+ * @NM_METERED_NO: Not metered, the value was statically set
+ * @NM_METERED_GUESS_YES: Metered, the value was guessed
+ * @NM_METERED_GUESS_NO: Not metered, the value was guessed
+ *
+ * (Corresponds to the NM_METERED type in nm-device.xml.)
+ *
+ * Since: 1.0.6
+ **/
+NM_AVAILABLE_IN_1_0_6
+typedef enum {
+ NM_METERED_UNKNOWN = 0,
+ NM_METERED_YES = 1,
+ NM_METERED_NO = 2,
+ NM_METERED_GUESS_YES = 3,
+ NM_METERED_GUESS_NO = 4,
+} NMMetered;
/**
* NMActiveConnectionState:
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index 07b3401d1e..28e590df37 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -76,6 +76,7 @@ typedef struct {
char *zone;
GSList *secondaries; /* secondary connections to activate with the base connection */
guint gateway_ping_timeout;
+ NMMetered metered;
} NMSettingConnectionPrivate;
enum {
@@ -95,6 +96,7 @@ enum {
PROP_AUTOCONNECT_SLAVES,
PROP_SECONDARIES,
PROP_GATEWAY_PING_TIMEOUT,
+ PROP_METERED,
LAST_PROP
};
@@ -760,6 +762,23 @@ nm_setting_connection_get_gateway_ping_timeout (NMSettingConnection *setting)
return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->gateway_ping_timeout;
}
+/**
+ * nm_setting_connection_get_metered:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns: the #NMSettingConnection:metered property of the setting.
+ *
+ * Since: 1.0.6
+ **/
+NMMetered
+nm_setting_connection_get_metered (NMSettingConnection *setting)
+{
+ g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting),
+ NM_METERED_UNKNOWN);
+
+ return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->metered;
+}
+
static void
_set_error_missing_base_setting (GError **error, const char *type)
{
@@ -921,6 +940,18 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
}
+ if (priv->metered != NM_METERED_UNKNOWN &&
+ priv->metered != NM_METERED_YES &&
+ priv->metered != NM_METERED_NO) {
+ g_set_error (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("metered value %d is not valid"), priv->metered);
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_CONNECTION_SETTING_NAME,
+ NM_SETTING_CONNECTION_METERED);
+ return FALSE;
+ }
+
/* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */
if (!priv->uuid) {
@@ -1154,6 +1185,9 @@ set_property (GObject *object, guint prop_id,
case PROP_GATEWAY_PING_TIMEOUT:
priv->gateway_ping_timeout = g_value_get_uint (value);
break;
+ case PROP_METERED:
+ priv->metered = g_value_get_enum (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1227,6 +1261,9 @@ get_property (GObject *object, guint prop_id,
case PROP_GATEWAY_PING_TIMEOUT:
g_value_set_uint (value, priv->gateway_ping_timeout);
break;
+ case PROP_METERED:
+ g_value_set_enum (value, priv->metered);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1626,4 +1663,27 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
+
+ /**
+ * NMSettingConnection:metered:
+ *
+ * Whether the connection is metered.
+ *
+ * Since: 1.0.6
+ **/
+ /* ---ifcfg-rh---
+ * property: metered
+ * variable: CONNECTION_METERED
+ * values: yes,no,unknown
+ * description: Whether the device is metered
+ * example: CONNECTION_METERED=yes
+ * ---end---
+ */
+ g_object_class_install_property
+ (object_class, PROP_METERED,
+ g_param_spec_enum (NM_SETTING_CONNECTION_METERED, "", "",
+ NM_TYPE_METERED,
+ NM_METERED_UNKNOWN,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
}
diff --git a/libnm-core/nm-setting-connection.h b/libnm-core/nm-setting-connection.h
index ff89d20081..24b5f6accc 100644
--- a/libnm-core/nm-setting-connection.h
+++ b/libnm-core/nm-setting-connection.h
@@ -59,6 +59,7 @@ G_BEGIN_DECLS
#define NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES "autoconnect-slaves"
#define NM_SETTING_CONNECTION_SECONDARIES "secondaries"
#define NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT "gateway-ping-timeout"
+#define NM_SETTING_CONNECTION_METERED "metered"
/* Types for property values */
/**
@@ -142,6 +143,8 @@ void nm_setting_connection_remove_secondary (NMSettingConnection *set
gboolean nm_setting_connection_remove_secondary_by_value (NMSettingConnection *setting, const char *sec_uuid);
guint32 nm_setting_connection_get_gateway_ping_timeout (NMSettingConnection *setting);
+NM_AVAILABLE_IN_1_0_6
+NMMetered nm_setting_connection_get_metered (NMSettingConnection *setting);
G_END_DECLS
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index bd144f384b..a077740dc9 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -1969,6 +1969,7 @@ test_connection_diff_a_only (void)
{ NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_CONNECTION_SECONDARIES, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A },
+ { NM_SETTING_CONNECTION_METERED, NM_SETTING_DIFF_RESULT_IN_A },
{ NULL, NM_SETTING_DIFF_RESULT_UNKNOWN }
} },
{ NM_SETTING_WIRED_SETTING_NAME, {
diff --git a/libnm/libnm.ver b/libnm/libnm.ver
index 294bab4109..ecdfc3416c 100644
--- a/libnm/libnm.ver
+++ b/libnm/libnm.ver
@@ -851,3 +851,10 @@ global:
nm_setting_connection_get_autoconnect_slaves;
} libnm_1_0_0;
+libnm_1_0_6 {
+global:
+ nm_device_get_metered;
+ nm_metered_get_type;
+ nm_setting_connection_get_metered;
+} libnm_1_0_4;
+
diff --git a/libnm/nm-client.c b/libnm/nm-client.c
index e0a2a7b4f4..2cdea31336 100644
--- a/libnm/nm-client.c
+++ b/libnm/nm-client.c
@@ -76,6 +76,7 @@ enum {
PROP_CONNECTIONS,
PROP_HOSTNAME,
PROP_CAN_MODIFY,
+ PROP_METERED,
LAST_PROP
};
@@ -1871,6 +1872,7 @@ get_property (GObject *object, guint prop_id,
case PROP_PRIMARY_CONNECTION:
case PROP_ACTIVATING_CONNECTION:
case PROP_DEVICES:
+ case PROP_METERED:
g_object_get_property (G_OBJECT (NM_CLIENT_GET_PRIVATE (object)->manager),
pspec->name, value);
break;
@@ -2143,6 +2145,20 @@ nm_client_class_init (NMClientClass *client_class)
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * NMClient:metered:
+ *
+ * Whether the connectivity is metered.
+ *
+ * Since: 1.0.6
+ **/
+ g_object_class_install_property
+ (object_class, PROP_METERED,
+ g_param_spec_uint (NM_CLIENT_METERED, "", "",
+ 0, G_MAXUINT32, NM_METERED_UNKNOWN,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
/* signals */
/**
diff --git a/libnm/nm-client.h b/libnm/nm-client.h
index 619ad8585f..fc309fa1a0 100644
--- a/libnm/nm-client.h
+++ b/libnm/nm-client.h
@@ -56,6 +56,7 @@ G_BEGIN_DECLS
#define NM_CLIENT_CONNECTIONS "connections"
#define NM_CLIENT_HOSTNAME "hostname"
#define NM_CLIENT_CAN_MODIFY "can-modify"
+#define NM_CLIENT_METERED "metered"
#define NM_CLIENT_DEVICE_ADDED "device-added"
#define NM_CLIENT_DEVICE_REMOVED "device-removed"
diff --git a/libnm/nm-device.c b/libnm/nm-device.c
index cd14c0e902..d3ac705c1c 100644
--- a/libnm/nm-device.c
+++ b/libnm/nm-device.c
@@ -81,6 +81,7 @@ typedef struct {
char *driver_version;
char *firmware_version;
char *type_description;
+ NMMetered metered;
NMDeviceCapabilities capabilities;
gboolean managed;
gboolean firmware_missing;
@@ -130,6 +131,7 @@ enum {
PROP_AVAILABLE_CONNECTIONS,
PROP_PHYSICAL_PORT_ID,
PROP_MTU,
+ PROP_METERED,
LAST_PROP
};
@@ -193,6 +195,7 @@ init_dbus (NMObject *object)
{ NM_DEVICE_AVAILABLE_CONNECTIONS, &priv->available_connections, NULL, NM_TYPE_REMOTE_CONNECTION },
{ NM_DEVICE_PHYSICAL_PORT_ID, &priv->physical_port_id },
{ NM_DEVICE_MTU, &priv->mtu },
+ { NM_DEVICE_METERED, &priv->metered },
/* Properties that exist in D-Bus but that we don't track */
{ "ip4-address", NULL },
@@ -449,6 +452,9 @@ get_property (GObject *object,
case PROP_MTU:
g_value_set_uint (value, nm_device_get_mtu (device));
break;
+ case PROP_METERED:
+ g_value_set_uint (value, nm_device_get_metered (device));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -793,6 +799,20 @@ nm_device_class_init (NMDeviceClass *device_class)
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * NMDevice:metered:
+ *
+ * Whether the device is metered.
+ *
+ * Since: 1.0.6
+ **/
+ g_object_class_install_property
+ (object_class, PROP_METERED,
+ g_param_spec_uint (NM_DEVICE_METERED, "", "",
+ 0, G_MAXUINT32, NM_METERED_UNKNOWN,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
/* signals */
/**
@@ -1883,6 +1903,24 @@ nm_device_get_mtu (NMDevice *device)
return NM_DEVICE_GET_PRIVATE (device)->mtu;
}
+/**
+ * nm_device_get_metered:
+ * @device: a #NMDevice
+ *
+ * Gets the metered setting of a #NMDevice.
+ *
+ * Returns: the metered setting.
+ *
+ * Since: 1.0.6
+ **/
+NMMetered
+nm_device_get_metered (NMDevice *device)
+{
+ g_return_val_if_fail (NM_IS_DEVICE (device), NM_METERED_UNKNOWN);
+
+ return NM_DEVICE_GET_PRIVATE (device)->metered;
+}
+
/**
* nm_device_is_software:
* @device: a #NMDevice
diff --git a/libnm/nm-device.h b/libnm/nm-device.h
index 5cc3735695..a61dcf3234 100644
--- a/libnm/nm-device.h
+++ b/libnm/nm-device.h
@@ -60,6 +60,7 @@ G_BEGIN_DECLS
#define NM_DEVICE_PRODUCT "product"
#define NM_DEVICE_PHYSICAL_PORT_ID "physical-port-id"
#define NM_DEVICE_MTU "mtu"
+#define NM_DEVICE_METERED "metered"
struct _NMDevice {
NMObject parent;
@@ -119,6 +120,8 @@ gboolean nm_device_is_software (NMDevice *device);
const char * nm_device_get_product (NMDevice *device);
const char * nm_device_get_vendor (NMDevice *device);
const char * nm_device_get_description (NMDevice *device);
+NM_AVAILABLE_IN_1_0_6
+NMMetered nm_device_get_metered (NMDevice *device);
char ** nm_device_disambiguate_names (NMDevice **devices,
int num_devices);
diff --git a/libnm/nm-manager.c b/libnm/nm-manager.c
index 003d6b8755..2a61a0109a 100644
--- a/libnm/nm-manager.c
+++ b/libnm/nm-manager.c
@@ -64,6 +64,7 @@ typedef struct {
NMConnectivityState connectivity;
NMActiveConnection *primary_connection;
NMActiveConnection *activating_connection;
+ NMMetered metered;
GCancellable *perm_call_cancellable;
GHashTable *permissions;
@@ -102,6 +103,7 @@ enum {
PROP_PRIMARY_CONNECTION,
PROP_ACTIVATING_CONNECTION,
PROP_DEVICES,
+ PROP_METERED,
LAST_PROP
};
@@ -179,6 +181,7 @@ init_dbus (NMObject *object)
{ NM_MANAGER_PRIMARY_CONNECTION, &priv->primary_connection, NULL, NM_TYPE_ACTIVE_CONNECTION },
{ NM_MANAGER_ACTIVATING_CONNECTION, &priv->activating_connection, NULL, NM_TYPE_ACTIVE_CONNECTION },
{ NM_MANAGER_DEVICES, &priv->devices, NULL, NM_TYPE_DEVICE, "device" },
+ { NM_MANAGER_METERED, &priv->metered },
{ NULL },
};
@@ -1537,6 +1540,9 @@ get_property (GObject *object,
case PROP_DEVICES:
g_value_take_boxed (value, _nm_utils_copy_object_array (nm_manager_get_devices (self)));
break;
+ case PROP_METERED:
+ g_value_set_uint (value, priv->metered);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1669,6 +1675,19 @@ nm_manager_class_init (NMManagerClass *manager_class)
G_TYPE_PTR_ARRAY,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * NMManager:metered:
+ *
+ * Whether the connectivity is metered.
+ *
+ * Since: 1.0.6
+ **/
+ g_object_class_install_property
+ (object_class, PROP_METERED,
+ g_param_spec_uint (NM_MANAGER_METERED, "", "",
+ 0, G_MAXUINT32, NM_METERED_UNKNOWN,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
/* signals */
diff --git a/libnm/nm-manager.h b/libnm/nm-manager.h
index ca9f7dd60b..8d04a060f6 100644
--- a/libnm/nm-manager.h
+++ b/libnm/nm-manager.h
@@ -50,6 +50,7 @@ G_BEGIN_DECLS
#define NM_MANAGER_PRIMARY_CONNECTION "primary-connection"
#define NM_MANAGER_ACTIVATING_CONNECTION "activating-connection"
#define NM_MANAGER_DEVICES "devices"
+#define NM_MANAGER_METERED "metered"
typedef struct {
NMObject parent;
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 75f4a2972d..bf4ca8e6e0 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -75,6 +75,7 @@ _LOG_DECLARE_SELF (NMDevice);
static void impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context);
static void impl_device_delete (NMDevice *self, DBusGMethodInvocation *context);
static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
+static void nm_device_update_metered (NMDevice *self);
#include "nm-device-glue.h"
@@ -128,6 +129,7 @@ enum {
PROP_MASTER,
PROP_HW_ADDRESS,
PROP_HAS_PENDING_ACTION,
+ PROP_METERED,
LAST_PROP
};
@@ -338,6 +340,8 @@ typedef struct {
gboolean is_master;
GSList * slaves; /* list of SlaveInfo */
+ NMMetered metered;
+
NMConnectionProvider *con_provider;
} NMDevicePrivate;
@@ -687,6 +691,21 @@ nm_device_get_device_type (NMDevice *self)
return NM_DEVICE_GET_PRIVATE (self)->type;
}
+/**
+ * nm_device_get_metered:
+ * @setting: the #NMDevice
+ *
+ * Returns: the #NMDevice:metered property of the device.
+ *
+ * Since: 1.0.6
+ **/
+NMMetered
+nm_device_get_metered (NMDevice *self)
+{
+ g_return_val_if_fail (NM_IS_DEVICE (self), NM_METERED_UNKNOWN);
+
+ return NM_DEVICE_GET_PRIVATE (self)->metered;
+}
/**
* nm_device_get_priority():
@@ -3450,8 +3469,10 @@ dhcp4_state_changed (NMDhcpClient *client,
if (priv->ip4_state == IP_CONF)
nm_device_activate_schedule_ip4_config_result (self, ip4_config);
- else if (priv->ip4_state == IP_DONE)
+ else if (priv->ip4_state == IP_DONE) {
dhcp4_lease_change (self, ip4_config);
+ nm_device_update_metered (self);
+ }
break;
case NM_DHCP_STATE_TIMEOUT:
dhcp4_fail (self, TRUE);
@@ -7497,6 +7518,59 @@ nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr)
priv->dhcp_anycast_address = g_strdup (addr);
}
+static void
+nm_device_update_metered (NMDevice *self)
+{
+#define NM_METERED_INVALID ((NMMetered) -1)
+ NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+ NMSettingConnection *setting;
+ NMMetered conn_value, value = NM_METERED_INVALID;
+ NMConnection *connection = NULL;
+ NMDeviceState state;
+
+ g_return_if_fail (NM_IS_DEVICE (self));
+
+ state = nm_device_get_state (self);
+ if ( state <= NM_DEVICE_STATE_DISCONNECTED
+ || state > NM_DEVICE_STATE_ACTIVATED)
+ value = NM_METERED_UNKNOWN;
+
+ if (value == NM_METERED_INVALID) {
+ connection = nm_device_get_connection (self);
+ if (connection) {
+ setting = nm_connection_get_setting_connection (connection);
+ if (setting) {
+ conn_value = nm_setting_connection_get_metered (setting);
+ if (conn_value != NM_METERED_UNKNOWN)
+ value = conn_value;
+ }
+ }
+ }
+
+ /* Try to guess a value using the metered flag in IP configuration */
+ if (value == NM_METERED_INVALID) {
+ if ( priv->ip4_config
+ && priv->ip4_state == IP_DONE
+ && nm_ip4_config_get_metered (priv->ip4_config))
+ value = NM_METERED_GUESS_YES;
+ }
+
+ /* Otherwise look at connection type */
+ if (value == NM_METERED_INVALID) {
+ if ( nm_connection_is_type (connection, NM_SETTING_GSM_SETTING_NAME)
+ || nm_connection_is_type (connection, NM_SETTING_CDMA_SETTING_NAME))
+ value = NM_METERED_GUESS_YES;
+ else
+ value = NM_METERED_GUESS_NO;
+ }
+
+ if (value != priv->metered) {
+ _LOGD (LOGD_DEVICE, "set metered value %d", value);
+ priv->metered = value;
+ g_object_notify (G_OBJECT (self), NM_DEVICE_METERED);
+ }
+}
+
/**
* nm_device_check_connection_available():
* @self: the #NMDevice
@@ -7954,6 +8028,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
nm_platform_address_flush (NM_PLATFORM_GET, ifindex);
}
+ nm_device_update_metered (self);
_cleanup_generic_post (self, cleanup_type);
}
@@ -8426,6 +8501,7 @@ _set_state_full (NMDevice *self,
break;
case NM_DEVICE_STATE_ACTIVATED:
_LOGI (LOGD_DEVICE, "Activation: successful, device activated.");
+ nm_device_update_metered (self);
nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), self, NULL, NULL, NULL);
break;
case NM_DEVICE_STATE_FAILED:
@@ -9298,6 +9374,9 @@ get_property (GObject *object, guint prop_id,
case PROP_HAS_PENDING_ACTION:
g_value_set_boolean (value, nm_device_has_pending_action (self));
break;
+ case PROP_METERED:
+ g_value_set_uint (value, priv->metered);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -9561,6 +9640,20 @@ nm_device_class_init (NMDeviceClass *klass)
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * NMDevice:metered:
+ *
+ * Whether the connection is metered.
+ *
+ * Since: 1.0.6
+ **/
+ g_object_class_install_property
+ (object_class, PROP_METERED,
+ g_param_spec_uint (NM_DEVICE_METERED, "", "",
+ 0, G_MAXUINT32, NM_METERED_UNKNOWN,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
/* Signals */
signals[STATE_CHANGED] =
g_signal_new ("state-changed",
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index f1dab9bf21..e9d5b9488d 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -58,6 +58,7 @@
#define NM_DEVICE_PHYSICAL_PORT_ID "physical-port-id"
#define NM_DEVICE_MTU "mtu"
#define NM_DEVICE_HW_ADDRESS "hw-address"
+#define NM_DEVICE_METERED "metered"
#define NM_DEVICE_TYPE_DESC "type-desc" /* Internal only */
#define NM_DEVICE_RFKILL_TYPE "rfkill-type" /* Internal only */
@@ -74,7 +75,6 @@
#define NM_DEVICE_RECHECK_AUTO_ACTIVATE "recheck-auto-activate"
#define NM_DEVICE_RECHECK_ASSUME "recheck-assume"
-
G_BEGIN_DECLS
#define NM_TYPE_DEVICE (nm_device_get_type ())
@@ -281,6 +281,7 @@ const char * nm_device_get_driver_version (NMDevice *dev);
const char * nm_device_get_type_desc (NMDevice *dev);
const char * nm_device_get_type_description (NMDevice *dev);
NMDeviceType nm_device_get_device_type (NMDevice *dev);
+NMMetered nm_device_get_metered (NMDevice *dev);
int nm_device_get_priority (NMDevice *dev);
guint32 nm_device_get_ip4_route_metric (NMDevice *dev);
diff --git a/src/dhcp-manager/nm-dhcp-systemd.c b/src/dhcp-manager/nm-dhcp-systemd.c
index 12dc03cc84..2bd0d72ffc 100644
--- a/src/dhcp-manager/nm-dhcp-systemd.c
+++ b/src/dhcp-manager/nm-dhcp-systemd.c
@@ -224,6 +224,8 @@ lease_to_ip4_config (sd_dhcp_lease *lease,
guint16 mtu;
int r, num;
guint64 end_time;
+ uint8_t *data;
+ gboolean metered = FALSE;
g_return_val_if_fail (lease != NULL, NULL);
@@ -355,6 +357,11 @@ lease_to_ip4_config (sd_dhcp_lease *lease,
g_string_free (l, TRUE);
}
+ num = sd_dhcp_lease_get_vendor_specific (lease, &data);
+ if (num > 0)
+ metered = !!memmem (data, num, "ANDROID_METERED", STRLEN ("ANDROID_METERED"));
+ nm_ip4_config_set_metered (ip4_config, metered);
+
return ip4_config;
}
diff --git a/src/dhcp-manager/nm-dhcp-utils.c b/src/dhcp-manager/nm-dhcp-utils.c
index 8cd4359abb..ab7f26d5fa 100644
--- a/src/dhcp-manager/nm-dhcp-utils.c
+++ b/src/dhcp-manager/nm-dhcp-utils.c
@@ -575,6 +575,9 @@ nm_dhcp_utils_ip4_config_from_options (const char *iface,
g_strfreev (nis);
}
+ str = g_hash_table_lookup (options, "vendor_encapsulated_options");
+ nm_ip4_config_set_metered (ip4_config, str && strstr (str, "ANDROID_METERED"));
+
return ip4_config;
error:
diff --git a/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-lease-internal.h b/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-lease-internal.h
index 9e184ac4b5..71f7f143b3 100644
--- a/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-lease-internal.h
@@ -72,6 +72,8 @@ struct sd_dhcp_lease {
char *root_path;
uint8_t *client_id;
size_t client_id_len;
+ uint8_t *vendor_specific;
+ size_t vendor_specific_size;
};
int dhcp_lease_new(sd_dhcp_lease **ret);
diff --git a/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-protocol.h b/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-protocol.h
index abca9422c5..aa37e9b0b5 100644
--- a/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-protocol.h
+++ b/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-protocol.h
@@ -125,6 +125,7 @@ enum {
DHCP_OPTION_BROADCAST = 28,
DHCP_OPTION_STATIC_ROUTE = 33,
DHCP_OPTION_NTP_SERVER = 42,
+ DHCP_OPTION_VENDOR_SPECIFIC = 43,
DHCP_OPTION_REQUESTED_IP_ADDRESS = 50,
DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51,
DHCP_OPTION_OVERLOAD = 52,
diff --git a/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp-lease.c b/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp-lease.c
index 2d13d503e1..0b5048a663 100644
--- a/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp-lease.c
@@ -191,6 +191,19 @@ int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routes
return 0;
}
+int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, uint8_t **data) {
+ assert_return(lease, -EINVAL);
+ assert_return(data, -EINVAL);
+
+ if (lease->vendor_specific) {
+ *data = lease->vendor_specific;
+ return lease->vendor_specific_size;
+ } else
+ return -ENOENT;
+
+ return 0;
+}
+
sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease) {
if (lease)
assert_se(REFCNT_INC(lease->n_ref) >= 2);
@@ -286,6 +299,24 @@ static int lease_parse_string(const uint8_t *option, size_t len, char **ret) {
return 0;
}
+static int lease_parse_binary(const uint8_t *option, size_t len, uint8_t **ret) {
+ assert (option);
+ assert (ret);
+
+ if (len >= 1) {
+ uint8_t *data;
+
+ data = memdup(option, len);
+ if (!data)
+ return -errno;
+
+ free(*ret);
+ *ret = data;
+ }
+
+ return 0;
+}
+
static int lease_parse_in_addrs_aux(const uint8_t *option, size_t len, struct in_addr **ret, size_t *ret_size, size_t mult) {
assert(option);
assert(ret);
@@ -568,6 +599,14 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const uint8_t *option,
return r;
break;
+
+ case DHCP_OPTION_VENDOR_SPECIFIC:
+ r = lease_parse_binary(option, len, &lease->vendor_specific);
+ if (r < 0)
+ return r;
+ lease->vendor_specific_size = len;
+
+ break;
}
return 0;
@@ -595,6 +634,7 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
const uint8_t *client_id;
size_t client_id_len;
const char *string;
+ uint8_t *data;
uint16_t mtu;
struct sd_dhcp_route *routes;
int r;
@@ -667,6 +707,18 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
if (r >= 0)
serialize_dhcp_routes(f, "ROUTES", routes, r);
+ r = sd_dhcp_lease_get_vendor_specific(lease, &data);
+ if (r >= 0) {
+ _cleanup_free_ char *option_hex = NULL;
+
+ option_hex = hexmem(data, r);
+ if (!option_hex) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex);
+ }
+
r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len);
if (r >= 0) {
_cleanup_free_ char *client_id_hex = NULL;
diff --git a/src/dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp-lease.h b/src/dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp-lease.h
index 4296b91d8a..079a1bb347 100644
--- a/src/dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp-lease.h
+++ b/src/dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp-lease.h
@@ -45,6 +45,7 @@ int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname);
int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path);
int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routesgn);
+int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, uint8_t **data);
int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const uint8_t **client_id,
size_t *client_id_len);
diff --git a/src/dhcp-manager/tests/test-dhcp-utils.c b/src/dhcp-manager/tests/test-dhcp-utils.c
index 53688a5b1b..e022a1ae74 100644
--- a/src/dhcp-manager/tests/test-dhcp-utils.c
+++ b/src/dhcp-manager/tests/test-dhcp-utils.c
@@ -175,6 +175,30 @@ test_wins_options (void)
g_hash_table_destroy (options);
}
+static void
+test_vendor_option_metered (void)
+{
+ GHashTable *options;
+ NMIP4Config *ip4_config;
+ static const Option data[] = {
+ { "vendor_encapsulated_options", "ANDROID_METERED" },
+ { NULL, NULL }
+ };
+
+ options = fill_table (generic_options, NULL);
+ ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0);
+ g_assert (ip4_config);
+ g_assert (nm_ip4_config_get_metered (ip4_config) == FALSE);
+ g_hash_table_destroy (options);
+
+ options = fill_table (generic_options, NULL);
+ options = fill_table (data, options);
+ ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0);
+ g_assert (ip4_config);
+ g_assert (nm_ip4_config_get_metered (ip4_config) == TRUE);
+ g_hash_table_destroy (options);
+}
+
static void
ip4_test_route (NMIP4Config *ip4_config,
guint route_num,
@@ -716,6 +740,7 @@ int main (int argc, char **argv)
g_test_add_func ("/dhcp/ip4-missing-prefix-8", test_ip4_missing_prefix_8);
g_test_add_func ("/dhcp/ip4-prefix-classless", test_ip4_prefix_classless);
g_test_add_func ("/dhcp/client-id-from-string", test_client_id_from_string);
+ g_test_add_func ("/dhcp/vendor-option-metered", test_vendor_option_metered);
return g_test_run ();
}
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index d40d571e8a..b0bdd77a81 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -100,6 +100,7 @@ enum {
enum {
DEVICE_CHANGED,
+ DEVICE_METERED_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
@@ -401,6 +402,18 @@ device_master_changed (GObject *object,
}
}
+static void
+device_metered_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NMActiveConnection *self = (NMActiveConnection *) user_data;
+ NMDevice *device = NM_DEVICE (object);
+
+ g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));
+ g_signal_emit (self, signals[DEVICE_METERED_CHANGED], 0, nm_device_get_metered (device));
+}
+
gboolean
nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device)
{
@@ -427,6 +440,8 @@ nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device)
G_CALLBACK (device_state_changed), self);
g_signal_connect (device, "notify::master",
G_CALLBACK (device_master_changed), self);
+ g_signal_connect (device, "notify::" NM_DEVICE_METERED,
+ G_CALLBACK (device_metered_changed), self);
if (!priv->assumed) {
priv->pending_activation_id = g_strdup_printf ("activation::%p", (void *)self);
@@ -838,6 +853,7 @@ _device_cleanup (NMActiveConnection *self)
if (priv->device) {
g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_state_changed), self);
g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_master_changed), self);
+ g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_metered_changed), self);
}
if (priv->pending_activation_id) {
@@ -1043,6 +1059,14 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
NULL, NULL, NULL,
G_TYPE_NONE, 2, NM_TYPE_DEVICE, NM_TYPE_DEVICE);
+ signals[DEVICE_METERED_CHANGED] =
+ g_signal_new (NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED,
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (NMActiveConnectionClass, device_metered_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
G_TYPE_FROM_CLASS (ac_class),
&dbus_glib_nm_active_connection_object_info);
diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h
index 710cfeed9f..4db5e6ff56 100644
--- a/src/nm-active-connection.h
+++ b/src/nm-active-connection.h
@@ -57,7 +57,8 @@
#define NM_ACTIVE_CONNECTION_INT_MASTER_READY "int-master-ready"
/* Internal signals*/
-#define NM_ACTIVE_CONNECTION_DEVICE_CHANGED "device-changed"
+#define NM_ACTIVE_CONNECTION_DEVICE_CHANGED "device-changed"
+#define NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED "device-metered-changed"
struct _NMActiveConnection {
GObject parent;
@@ -78,6 +79,9 @@ typedef struct {
void (*device_changed) (NMActiveConnection *connection,
NMDevice *new_device,
NMDevice *old_device);
+
+ void (*device_metered_changed) (NMActiveConnection *connection,
+ NMMetered new_value);
} NMActiveConnectionClass;
GType nm_active_connection_get_type (void);
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index db8c4b43e6..4c3ea8bd27 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -56,6 +56,7 @@ typedef struct {
GArray *wins;
guint32 mtu;
NMIPConfigSource mtu_source;
+ gboolean metered;
} NMIP4ConfigPrivate;
/* internal guint32 are assigned to gobject properties of type uint. Ensure, that uint is large enough */
@@ -574,6 +575,10 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src)
for (i = 0; i < nm_ip4_config_get_num_wins (src); i++)
nm_ip4_config_add_wins (dst, nm_ip4_config_get_wins (src, i));
+ /* metered flag */
+ nm_ip4_config_set_metered (dst, nm_ip4_config_get_metered (dst) ||
+ nm_ip4_config_get_metered (src));
+
g_object_thaw_notify (G_OBJECT (dst));
}
@@ -1028,6 +1033,12 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
has_minor_changes = TRUE;
}
+ /* metered */
+ if (src_priv->metered != dst_priv->metered) {
+ dst_priv->metered = src_priv->metered;
+ has_minor_changes = TRUE;
+ }
+
/* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes
* regardless of config_equal. But config_equal must correspond to has_relevant_changes. */
g_assert (config_equal == !has_relevant_changes);
@@ -1098,6 +1109,7 @@ nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
}
g_message (" n-dflt: %d", nm_ip4_config_get_never_default (config));
+ g_message (" mtrd: %d", (int) nm_ip4_config_get_metered (config));
}
gboolean
@@ -1744,6 +1756,24 @@ nm_ip4_config_get_mtu_source (const NMIP4Config *config)
/******************************************************************/
+void
+nm_ip4_config_set_metered (NMIP4Config *config, gboolean metered)
+{
+ NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+ priv->metered = !!metered;
+}
+
+gboolean
+nm_ip4_config_get_metered (const NMIP4Config *config)
+{
+ NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+
+ return priv->metered;
+}
+
+/******************************************************************/
+
static inline void
hash_u32 (GChecksum *sum, guint32 n)
{
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 463675244e..62a309fa08 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -146,6 +146,10 @@ void nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu, NMIPConfigSource s
guint32 nm_ip4_config_get_mtu (const NMIP4Config *config);
NMIPConfigSource nm_ip4_config_get_mtu_source (const NMIP4Config *config);
+/* Metered */
+void nm_ip4_config_set_metered (NMIP4Config *config, gboolean metered);
+gboolean nm_ip4_config_get_metered (const NMIP4Config *config);
+
void nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only);
gboolean nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b);
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 080cdb8b24..34078a0094 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -161,6 +161,7 @@ typedef struct {
guint ac_cleanup_id;
NMActiveConnection *primary_connection;
NMActiveConnection *activating_connection;
+ NMMetered metered;
GSList *devices;
NMState state;
@@ -233,6 +234,7 @@ enum {
PROP_PRIMARY_CONNECTION_TYPE,
PROP_ACTIVATING_CONNECTION,
PROP_DEVICES,
+ PROP_METERED,
/* Not exported */
PROP_HOSTNAME,
@@ -657,6 +659,30 @@ find_best_device_state (NMManager *manager)
return best_state;
}
+static void
+nm_manager_update_metered (NMManager *manager)
+{
+ NMManagerPrivate *priv;
+ NMDevice *device;
+ NMMetered value = NM_METERED_UNKNOWN;
+
+ g_return_if_fail (NM_IS_MANAGER (manager));
+ priv = NM_MANAGER_GET_PRIVATE (manager);
+
+ if (priv->primary_connection) {
+ device = nm_active_connection_get_device (priv->primary_connection);
+ if (device)
+ value = nm_device_get_metered (device);
+ }
+
+ if (value != priv->metered) {
+ priv->metered = value;
+ nm_log_dbg (LOGD_CORE, "New manager metered value: %d",
+ (int) priv->metered);
+ g_object_notify (G_OBJECT (manager), NM_MANAGER_METERED);
+ }
+}
+
static void
nm_manager_update_state (NMManager *manager)
{
@@ -4259,6 +4285,14 @@ firmware_dir_changed (GFileMonitor *monitor,
}
}
+static void
+connection_metered_changed (GObject *object,
+ NMMetered metered,
+ gpointer user_data)
+{
+ nm_manager_update_metered (NM_MANAGER (user_data));
+}
+
static void
policy_default_device_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
{
@@ -4281,11 +4315,23 @@ policy_default_device_changed (GObject *object, GParamSpec *pspec, gpointer user
ac = NULL;
if (ac != priv->primary_connection) {
- g_clear_object (&priv->primary_connection);
+ if (priv->primary_connection) {
+ g_signal_handlers_disconnect_by_func (priv->primary_connection,
+ G_CALLBACK (connection_metered_changed),
+ self);
+ g_clear_object (&priv->primary_connection);
+ }
+
priv->primary_connection = ac ? g_object_ref (ac) : NULL;
+
+ if (priv->primary_connection) {
+ g_signal_connect (priv->primary_connection, NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED,
+ G_CALLBACK (connection_metered_changed), self);
+ }
nm_log_dbg (LOGD_CORE, "PrimaryConnection now %s", ac ? nm_active_connection_get_id (ac) : "(none)");
g_object_notify (G_OBJECT (self), NM_MANAGER_PRIMARY_CONNECTION);
g_object_notify (G_OBJECT (self), NM_MANAGER_PRIMARY_CONNECTION_TYPE);
+ nm_manager_update_metered (self);
}
}
@@ -4879,6 +4925,8 @@ nm_manager_init (NMManager *manager)
/* Update timestamps in active connections */
priv->timestamp_update_id = g_timeout_add_seconds (300, (GSourceFunc) periodic_update_active_connection_timestamps, manager);
+
+ priv->metered = NM_METERED_UNKNOWN;
}
static void
@@ -4963,6 +5011,9 @@ get_property (GObject *object, guint prop_id,
}
g_value_take_boxed (value, array);
break;
+ case PROP_METERED:
+ g_value_set_uint (value, priv->metered);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -5238,6 +5289,20 @@ nm_manager_class_init (NMManagerClass *manager_class)
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * NMManager:metered:
+ *
+ * Whether the connectivity is metered.
+ *
+ * Since: 1.0.6
+ **/
+ g_object_class_install_property
+ (object_class, PROP_METERED,
+ g_param_spec_uint (NM_MANAGER_METERED, "", "",
+ 0, G_MAXUINT32, NM_METERED_UNKNOWN,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
/* signals */
signals[DEVICE_ADDED] =
g_signal_new ("device-added",
diff --git a/src/nm-manager.h b/src/nm-manager.h
index 3b00e8053a..a8e7d7ad32 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -51,6 +51,7 @@
#define NM_MANAGER_PRIMARY_CONNECTION_TYPE "primary-connection-type"
#define NM_MANAGER_ACTIVATING_CONNECTION "activating-connection"
#define NM_MANAGER_DEVICES "devices"
+#define NM_MANAGER_METERED "metered"
/* Not exported */
#define NM_MANAGER_HOSTNAME "hostname"
diff --git a/src/settings/plugins/ifcfg-rh/reader.c b/src/settings/plugins/ifcfg-rh/reader.c
index ec87416775..170b4b2905 100644
--- a/src/settings/plugins/ifcfg-rh/reader.c
+++ b/src/settings/plugins/ifcfg-rh/reader.c
@@ -250,6 +250,15 @@ make_connection_setting (const char *file,
g_free (value);
}
+ switch (svTrueValue (ifcfg, "CONNECTION_METERED", -1)) {
+ case TRUE:
+ g_object_set (s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_YES, NULL);
+ break;
+ case FALSE:
+ g_object_set (s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_NO, NULL);
+ break;
+ }
+
return NM_SETTING (s_con);
}
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 4a5ca1d186..283aa82611 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -303,11 +303,11 @@ svGetValueFull (shvarFile *s, const char *key, gboolean verbatim)
* return FALSE if resolves to any non-truth value (e.g. "no", "n", "false")
* return otherwise
*/
-gboolean
-svTrueValue (shvarFile *s, const char *key, gboolean def)
+gint
+svTrueValue (shvarFile *s, const char *key, gint def)
{
char *tmp;
- gboolean returnValue = def;
+ gint returnValue = def;
tmp = svGetValue (s, key, FALSE);
if (!tmp)
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index 4902541b00..de7a358556 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -62,7 +62,7 @@ char *svGetValueFull (shvarFile *s, const char *key, gboolean verbatim);
* return FALSE if resolves to any non-truth value (e.g. "no", "n", "false")
* return otherwise
*/
-gboolean svTrueValue (shvarFile *s, const char *key, gboolean def);
+gint svTrueValue (shvarFile *s, const char *key, gint def);
gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback);
diff --git a/src/settings/plugins/ifcfg-rh/writer.c b/src/settings/plugins/ifcfg-rh/writer.c
index 52bf51d18b..146cd34096 100644
--- a/src/settings/plugins/ifcfg-rh/writer.c
+++ b/src/settings/plugins/ifcfg-rh/writer.c
@@ -1764,6 +1764,17 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
svSetValue (ifcfg, "GATEWAY_PING_TIMEOUT", tmp, FALSE);
g_free (tmp);
}
+
+ switch (nm_setting_connection_get_metered (s_con)) {
+ case NM_METERED_YES:
+ svSetValue (ifcfg, "CONNECTION_METERED", "yes", FALSE);
+ break;
+ case NM_METERED_NO:
+ svSetValue (ifcfg, "CONNECTION_METERED", "no", FALSE);
+ break;
+ default:
+ svSetValue (ifcfg, "CONNECTION_METERED", NULL, FALSE);
+ }
}
static gboolean