diff options
| author | Michael Biebl <biebl@debian.org> | 2018-09-08 17:44:06 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2018-09-08 17:44:06 +0200 |
| commit | 8f7a3cbbdd0c0a48277c341dd3a8ec8743ae9735 (patch) | |
| tree | 4353551fcb59cc822c3cadf2f4888f70601e8fbf /src/platform/nm-platform-utils.c | |
| parent | caf1db9d6fbc056cc6c76a24574890f6c7895f3d (diff) | |
New upstream version 1.13.90 upstream/1.13.90
Diffstat (limited to 'src/platform/nm-platform-utils.c')
| -rw-r--r-- | src/platform/nm-platform-utils.c | 737 |
1 files changed, 618 insertions, 119 deletions
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c index 0d224443..216b1547 100644 --- a/src/platform/nm-platform-utils.c +++ b/src/platform/nm-platform-utils.c @@ -36,12 +36,15 @@ #include "nm-utils.h" #include "nm-setting-wired.h" +#include "nm-ethtool-utils.h" #include "nm-core-utils.h" -/****************************************************************** +#define ONOFF(bool_val) ((bool_val) ? "on" : "off") + +/****************************************************************************** * utils - ******************************************************************/ + *****************************************************************************/ extern char *if_indextoname (unsigned __ifindex, char *__ifname); unsigned if_nametoindex (const char *__ifname); @@ -63,9 +66,45 @@ nmp_utils_if_nametoindex (const char *ifname) return if_nametoindex (ifname); } -/****************************************************************** +/*****************************************************************************/ + +typedef struct { + int fd; + int ifindex; + char ifname[IFNAMSIZ]; +} SocketHandle; + +static int +socket_handle_init (SocketHandle *shandle, int ifindex) +{ + if (!nmp_utils_if_indextoname (ifindex, shandle->ifname)) { + shandle->ifindex = 0; + return -ENODEV; + } + + shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (shandle->fd < 0) { + shandle->ifindex = 0; + return -errno; + } + + shandle->ifindex = ifindex; + return 0; +} + +static void +socket_handle_destroy (SocketHandle *shandle) +{ + if (shandle->ifindex) { + shandle->ifindex = 0; + nm_close (shandle->fd); + } +} +#define nm_auto_socket_handle nm_auto(socket_handle_destroy) + +/****************************************************************************** * ethtool - ******************************************************************/ + *****************************************************************************/ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32, NM_UTILS_ENUM2STR (ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"), @@ -77,6 +116,7 @@ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32, NM_UTILS_ENUM2STR (ETHTOOL_GSTATS, "ETHTOOL_GSTATS"), NM_UTILS_ENUM2STR (ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"), NM_UTILS_ENUM2STR (ETHTOOL_GWOL, "ETHTOOL_GWOL"), + NM_UTILS_ENUM2STR (ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"), NM_UTILS_ENUM2STR (ETHTOOL_SSET, "ETHTOOL_SSET"), NM_UTILS_ENUM2STR (ETHTOOL_SWOL, "ETHTOOL_SWOL"), ); @@ -87,6 +127,8 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len) return _ethtool_cmd_to_string (*((guint32 *) edata), buf, len); } +/*****************************************************************************/ + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) #define ethtool_cmd_speed(pedata) ((pedata)->speed) @@ -94,117 +136,553 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len) G_STMT_START { (pedata)->speed = (guint16) (speed); } G_STMT_END #endif -static gboolean -ethtool_get (int ifindex, gpointer edata) +static int +ethtool_call_handle (SocketHandle *shandle, gpointer edata) { - char ifname[IFNAMSIZ]; + struct ifreq ifr = { + .ifr_data = edata, + }; char sbuf[50]; + int errsv; + + nm_assert (shandle); + nm_assert (shandle->ifindex); + nm_assert (shandle->ifname[0]); + nm_assert (strlen (shandle->ifname) < IFNAMSIZ); + nm_assert (edata); + + memcpy (ifr.ifr_name, shandle->ifname, IFNAMSIZ); + if (ioctl (shandle->fd, SIOCETHTOOL, &ifr) < 0) { + errsv = errno; + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s", + shandle->ifindex, + _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)), + shandle->ifname, + strerror (errsv)); + return -errsv; + } - nm_assert (ifindex > 0); + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success", + shandle->ifindex, + _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)), + shandle->ifname); + return 0; +} - /* ethtool ioctl API uses the ifname to refer to an interface. That is racy - * as interfaces can be renamed *sigh*. - * - * Note that we anyway have to verify whether the interface exists, before - * calling ioctl for a non-existing ifname. This is to prevent autoloading - * of kernel modules *sigh*. - * Thus, as we anyway verify the existence of ifname before doing the call, - * go one step further and lookup the ifname everytime anew. - * - * This does not solve the renaming race, but it minimizes the time for - * the race to happen as much as possible. */ +static int +ethtool_call_ifindex (int ifindex, gpointer edata) +{ + nm_auto_socket_handle SocketHandle shandle = { }; + int r; + char sbuf[50]; - if (!nmp_utils_if_indextoname (ifindex, ifname)) { - nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: request fails resolving ifindex: %s", + nm_assert (edata); + + if ((r = socket_handle_init (&shandle, ifindex)) < 0) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s", ifindex, _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)), - g_strerror (errno)); + g_strerror (-r)); + return r; + } + + return ethtool_call_handle (&shandle, edata); +} + +/*****************************************************************************/ + +static struct ethtool_gstrings * +ethtool_get_stringset (SocketHandle *shandle, int stringset_id) +{ + struct { + struct ethtool_sset_info info; + guint32 sentinel; + } sset_info = { }; + gs_free struct ethtool_gstrings *gstrings = NULL; + guint32 i, len; + + sset_info.info.cmd = ETHTOOL_GSSET_INFO; + sset_info.info.reserved = 0; + sset_info.info.sset_mask = (1ULL << stringset_id); + + if (ethtool_call_handle (shandle, &sset_info) < 0) + return NULL; + if (!sset_info.info.sset_mask) + return NULL; + + len = sset_info.info.data[0]; + + gstrings = g_malloc0 (sizeof (*gstrings) + (len * ETH_GSTRING_LEN)); + gstrings->cmd = ETHTOOL_GSTRINGS; + gstrings->string_set = stringset_id; + gstrings->len = len; + if (gstrings->len > 0) { + if (ethtool_call_handle (shandle, gstrings) < 0) + return NULL; + for (i = 0; i < gstrings->len; i++) { + /* ensure NUL terminated */ + gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0'; + } + } + + return g_steal_pointer (&gstrings); +} + +static int +ethtool_gstrings_find (const struct ethtool_gstrings *gstrings, const char *needle) +{ + guint32 i; + + /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. + * that means, we cannot possibly request longer names. */ + nm_assert (needle && strlen (needle) < ETH_GSTRING_LEN); + + for (i = 0; i < gstrings->len; i++) { + if (nm_streq ((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle)) + return i; + } + return -1; +} + +static int +ethtool_get_stringset_index (SocketHandle *shandle, int stringset_id, const char *needle) +{ + gs_free struct ethtool_gstrings *gstrings = NULL; + + /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. + * that means, we cannot possibly request longer names. */ + nm_assert (needle && strlen (needle) < ETH_GSTRING_LEN); + + gstrings = ethtool_get_stringset (shandle, stringset_id); + if (gstrings) + return ethtool_gstrings_find (gstrings, needle); + return -1; +} + +/*****************************************************************************/ + +static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = { +#define ETHT_FEAT(eid, ...) \ + { \ + .ethtool_id = eid, \ + .n_kernel_names = NM_NARG (__VA_ARGS__), \ + .kernel_names = ((const char *const[]) { __VA_ARGS__ }), \ + } + + /* the order does only matter for one thing: if it happens that more than one NMEthtoolID + * reference the same kernel-name, then the one that is mentioned *later* will win in + * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which + * refer to multiple features ("feature-tso"), while also having more specific ids + * ("feature-tx-tcp-segmentation"). */ + + /* names from ethtool utility, which are aliases for multiple features. */ + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather", + "tx-scatter-gather-fraglist"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TSO, "tx-tcp-segmentation", + "tx-tcp-ecn-segmentation", + "tx-tcp-mangleid-segmentation", + "tx-tcp6-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX, "tx-checksum-ipv4", + "tx-checksum-ip-generic", + "tx-checksum-ipv6", + "tx-checksum-fcoe-crc", + "tx-checksum-sctp"), + + /* names from ethtool utility, which are aliases for one feature. */ + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"), + + /* names of features, as known by kernel. */ + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"), + ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"), +}; + +/* the number of kernel features that we handle. It essentially is the sum of all + * kernel_names. So, all ethtool-ids that reference exactly one kernel-name + * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases + * for multiple kernel-names. */ +#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u) + +static void +_ASSERT_ethtool_feature_infos (void) +{ +#if NM_MORE_ASSERTS > 10 + guint i, k, n; + bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = { }; + + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM); + + n = 0; + for (i = 0; i < G_N_ELEMENTS (_ethtool_feature_infos); i++) { + NMEthtoolFeatureState kstate; + const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i]; + + g_assert (inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST); + g_assert (inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST); + g_assert (inf->n_kernel_names > 0); + + for (k = 0; k < i; k++) + g_assert (inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id); + + g_assert (!found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]); + found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = TRUE; + + kstate.idx_kernel_name = inf->n_kernel_names - 1; + g_assert ((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1)); + + n += inf->n_kernel_names; + for (k = 0; k < inf->n_kernel_names; k++) { + g_assert (nm_utils_strv_find_first ((char **) inf->kernel_names, + k, + inf->kernel_names[k]) < 0); + } + } + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) + g_assert (found[i]); + + g_assert (n == N_ETHTOOL_KERNEL_FEATURES); +#endif +} + +static NMEthtoolFeatureStates * +ethtool_get_features (SocketHandle *shandle) +{ + gs_free NMEthtoolFeatureStates *states = NULL; + gs_free struct ethtool_gstrings *ss_features = NULL; + + _ASSERT_ethtool_feature_infos (); + + ss_features = ethtool_get_stringset (shandle, ETH_SS_FEATURES); + if (!ss_features) + return NULL; + + if (ss_features->len > 0) { + gs_free struct ethtool_gfeatures *gfeatures = NULL; + guint idx; + const NMEthtoolFeatureState *states_list0 = NULL; + const NMEthtoolFeatureState *const*states_plist0 = NULL; + guint states_plist_n = 0; + + gfeatures = g_malloc0 ( sizeof (struct ethtool_gfeatures) + + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0]))); + + gfeatures->cmd = ETHTOOL_GFEATURES; + gfeatures->size = NM_DIV_ROUND_UP (ss_features->len, 32u); + if (ethtool_call_handle (shandle, gfeatures) < 0) + return NULL; + + for (idx = 0; idx < G_N_ELEMENTS (_ethtool_feature_infos); idx++) { + const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx]; + guint idx_kernel_name; + + for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) { + NMEthtoolFeatureState *kstate; + const char *kernel_name = info->kernel_names[idx_kernel_name]; + int i_feature; + guint i_block; + guint32 i_flag; + + i_feature = ethtool_gstrings_find (ss_features, kernel_name); + if (i_feature < 0) + continue; + + i_block = ((guint) i_feature) / 32u; + i_flag = (guint32) (1u << (((guint) i_feature) % 32u)); + + if (!states) { + states = g_malloc0 (sizeof (NMEthtoolFeatureStates) + + (N_ETHTOOL_KERNEL_FEATURES * sizeof (NMEthtoolFeatureState)) + + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos)) * sizeof (NMEthtoolFeatureState *))); + states_list0 = &states->states_list[0]; + states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES]; + states->n_ss_features = ss_features->len; + } + + nm_assert (states->n_states < N_ETHTOOL_KERNEL_FEATURES); + kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states]; + states->n_states++; + + kstate->info = info; + kstate->idx_ss_features = i_feature; + kstate->idx_kernel_name = idx_kernel_name; + kstate->available = !!(gfeatures->features[i_block].available & i_flag); + kstate->requested = !!(gfeatures->features[i_block].requested & i_flag); + kstate->active = !!(gfeatures->features[i_block].active & i_flag); + kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag); + + nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos)); + + if (!states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]) + states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = &states_plist0[states_plist_n]; + ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate; + states_plist_n++; + } + + if (states && states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]) { + nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos)); + nm_assert (!states_plist0[states_plist_n]); + states_plist_n++; + } + } + } + + return g_steal_pointer (&states); +} + +NMEthtoolFeatureStates * +nmp_utils_ethtool_get_features (int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = { }; + NMEthtoolFeatureStates *features; + int r; + + g_return_val_if_fail (ifindex > 0, 0); + + if ((r = socket_handle_init (&shandle, ifindex)) < 0) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s", + ifindex, + "get-features", + g_strerror (-r)); return FALSE; } - { - nm_auto_close int fd = -1; - struct ifreq ifr = { - .ifr_data = edata, - }; + features = ethtool_get_features (&shandle); - memcpy (ifr.ifr_name, ifname, sizeof (ifname)); + if (!features) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure getting features", + ifindex, + "get-features"); + return NULL; + } - fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (fd < 0) { - nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed creating socket for ioctl: %s", - ifindex, - _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)), - ifname, - g_strerror (errno)); - return FALSE; + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: retrieved kernel features", + ifindex, + "get-features"); + return features; +} + +static const char * +_ethtool_feature_state_to_string (char *buf, gsize buf_size, const NMEthtoolFeatureState *s, const char *prefix) +{ + int l; + + l = g_snprintf (buf, buf_size, + "%s %s%s", + prefix ?: "", + ONOFF (s->active), + (!s->available || s->never_changed) + ? ", [fixed]" + : ((s->requested != s->active) + ? (s->requested ? ", [requested on]" : ", [requested off]") + : "")); + nm_assert (l < buf_size); + return buf; +} + +gboolean +nmp_utils_ethtool_set_features (int ifindex, + const NMEthtoolFeatureStates *features, + const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */) +{ + nm_auto_socket_handle SocketHandle shandle = { }; + gs_free struct ethtool_sfeatures *sfeatures = NULL; + int r; + guint i, j; + struct { + const NMEthtoolFeatureState *f_state; + NMTernary requested; + } set_states[N_ETHTOOL_KERNEL_FEATURES]; + guint set_states_n = 0; + gboolean success = TRUE; + + g_return_val_if_fail (ifindex > 0, 0); + g_return_val_if_fail (features, 0); + g_return_val_if_fail (requested, 0); + + nm_assert (features->n_states <= N_ETHTOOL_KERNEL_FEATURES); + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) { + const NMEthtoolFeatureState *const*states_indexed; + + if (requested[i] == NM_TERNARY_DEFAULT) + continue; + + if (!(states_indexed = features->states_indexed[i])) { + if (do_set) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: set feature %s: skip (not found)", + ifindex, + "set-features", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname); + success = FALSE; + } + continue; } - if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) { - nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s", + for (j = 0; states_indexed[j]; j++) { + const NMEthtoolFeatureState *s = states_indexed[j]; + char sbuf[255]; + + if (set_states_n >= G_N_ELEMENTS (set_states)) + g_return_val_if_reached (FALSE); + + if (s->never_changed) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as never changed)", + ifindex, + "set-features", + do_set ? "set" : "reset", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + s->info->kernel_names[s->idx_kernel_name], + ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active), + _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:")); + continue; + } + + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s", ifindex, - _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)), - ifname, - strerror (errno)); - return FALSE; + "set-features", + do_set ? "set" : "reset", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + s->info->kernel_names[s->idx_kernel_name], + ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active), + _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:")); + + if ( do_set + && (!s->available || s->never_changed) + && (s->active != (requested[i] == NM_TERNARY_TRUE))) { + /* we request to change a flag which kernel reported as fixed. + * While the ethtool operation will silently succeed, mark the request + * as failure. */ + success = FALSE; + } + + set_states[set_states_n].f_state = s; + set_states[set_states_n].requested = requested[i]; + set_states_n++; } + } - nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success", + if (set_states_n == 0) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: no feature requested", ifindex, - _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)), - ifname); + "set-features"); return TRUE; } -} -static int -ethtool_get_stringset_index (int ifindex, int stringset_id, const char *string) -{ - gs_free struct ethtool_sset_info *info = NULL; - gs_free struct ethtool_gstrings *strings = NULL; - guint32 len, i; + if ((r = socket_handle_init (&shandle, ifindex)) < 0) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s", + ifindex, + "set-features", + g_strerror (-r)); + return FALSE; + } - g_return_val_if_fail (ifindex > 0, -1); + sfeatures = g_malloc0 (sizeof (struct ethtool_sfeatures) + + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0]))); + sfeatures->cmd = ETHTOOL_SFEATURES; + sfeatures->size = NM_DIV_ROUND_UP (features->n_ss_features, 32U); - info = g_malloc0 (sizeof (*info) + sizeof (guint32)); - info->cmd = ETHTOOL_GSSET_INFO; - info->reserved = 0; - info->sset_mask = 1ULL << stringset_id; + for (i = 0; i < set_states_n; i++) { + const NMEthtoolFeatureState *s = set_states[i].f_state; + guint i_block; + guint32 i_flag; + gboolean is_requested; - if (!ethtool_get (ifindex, info)) - return -1; - if (!info->sset_mask) - return -1; + i_block = s->idx_ss_features / 32u; + i_flag = (guint32) (1u << (s->idx_ss_features % 32u)); - len = info->data[0]; + sfeatures->features[i_block].valid |= i_flag; - strings = g_malloc0 (sizeof (*strings) + len * ETH_GSTRING_LEN); - strings->cmd = ETHTOOL_GSTRINGS; - strings->string_set = stringset_id; - strings->len = len; - if (!ethtool_get (ifindex, strings)) - return -1; + if (do_set) + is_requested = (set_states[i].requested == NM_TERNARY_TRUE); + else + is_requested = s->active; - for (i = 0; i < len; i++) { - if (!strcmp ((char *) &strings->data[i * ETH_GSTRING_LEN], string)) - return i; + if (is_requested) + sfeatures->features[i_block].requested |= i_flag; + else + sfeatures->features[i_block].requested &= ~i_flag; } - return -1; + if ((r = ethtool_call_handle (&shandle, sfeatures)) < 0) { + success = FALSE; + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)", + ifindex, + "set-features", + g_strerror (-r)); + return FALSE; + } + + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s", + ifindex, + "set-features", + success + ? "successfully setting features" + : "at least some of the features were not successfuly set"); + return success; } +/*****************************************************************************/ + gboolean nmp_utils_ethtool_get_driver_info (int ifindex, NMPUtilsEthtoolDriverInfo *data) { struct ethtool_drvinfo *drvinfo; - G_STATIC_ASSERT (sizeof (*data) == sizeof (*drvinfo)); - G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, driver) == offsetof (struct ethtool_drvinfo, driver)); - G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, version) == offsetof (struct ethtool_drvinfo, version)); - G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, fw_version) == offsetof (struct ethtool_drvinfo, fw_version)); - G_STATIC_ASSERT (sizeof (data->driver) == sizeof (drvinfo->driver)); - G_STATIC_ASSERT (sizeof (data->version) == sizeof (drvinfo->version)); - G_STATIC_ASSERT (sizeof (data->fw_version) == sizeof (drvinfo->fw_version)); + + G_STATIC_ASSERT_EXPR (sizeof (*data) == sizeof (*drvinfo)); + G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, driver) == offsetof (struct ethtool_drvinfo, driver)); + G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, version) == offsetof (struct ethtool_drvinfo, version)); + G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, fw_version) == offsetof (struct ethtool_drvinfo, fw_version)); + G_STATIC_ASSERT_EXPR (sizeof (data->driver) == sizeof (drvinfo->driver)); + G_STATIC_ASSERT_EXPR (sizeof (data->version) == sizeof (drvinfo->version)); + G_STATIC_ASSERT_EXPR (sizeof (data->fw_version) == sizeof (drvinfo->fw_version)); g_return_val_if_fail (ifindex > 0, FALSE); g_return_val_if_fail (data, FALSE); @@ -213,7 +691,7 @@ nmp_utils_ethtool_get_driver_info (int ifindex, memset (drvinfo, 0, sizeof (*drvinfo)); drvinfo->cmd = ETHTOOL_GDRVINFO; - return ethtool_get (ifindex, drvinfo); + return ethtool_call_ifindex (ifindex, drvinfo) >= 0; } gboolean @@ -233,7 +711,7 @@ nmp_utils_ethtool_get_permanent_address (int ifindex, edata.e.cmd = ETHTOOL_GPERMADDR; edata.e.size = NM_UTILS_HWADDR_LEN_MAX; - if (!ethtool_get (ifindex, &edata.e)) + if (ethtool_call_ifindex (ifindex, &edata.e) < 0) return FALSE; if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX) @@ -252,8 +730,8 @@ nmp_utils_ethtool_get_permanent_address (int ifindex, } return FALSE; } -not_all_0or1: +not_all_0or1: memcpy (buf, edata.e.data, edata.e.size); *length = edata.e.size; return TRUE; @@ -270,20 +748,30 @@ nmp_utils_ethtool_supports_carrier_detect (int ifindex) * assume the device supports carrier-detect, otherwise we assume it * doesn't. */ - return ethtool_get (ifindex, &edata); + return ethtool_call_ifindex (ifindex, &edata) >= 0; } gboolean nmp_utils_ethtool_supports_vlans (int ifindex) { + nm_auto_socket_handle SocketHandle shandle = { }; + int r; gs_free struct ethtool_gfeatures *features = NULL; int idx, block, bit, size; g_return_val_if_fail (ifindex > 0, FALSE); - idx = ethtool_get_stringset_index (ifindex, ETH_SS_FEATURES, "vlan-challenged"); - if (idx == -1) { - nm_log_dbg (LOGD_PLATFORM, "ethtool: vlan-challenged ethtool feature does not exist for %d?", ifindex); + if ((r = socket_handle_init (&shandle, ifindex)) < 0) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s", + ifindex, + "support-vlans", + g_strerror (-r)); + return FALSE; + } + + idx = ethtool_get_stringset_index (&shandle, ETH_SS_FEATURES, "vlan-challenged"); + if (idx < 0) { + nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: vlan-challenged ethtool feature does not exist?", ifindex); return FALSE; } @@ -295,7 +783,7 @@ nmp_utils_ethtool_supports_vlans (int ifindex) features->cmd = ETHTOOL_GFEATURES; features->size = size; - if (!ethtool_get (ifindex, features)) + if (ethtool_call_handle (&shandle, features) < 0) return FALSE; return !(features->features[block].active & (1 << bit)); @@ -304,21 +792,32 @@ nmp_utils_ethtool_supports_vlans (int ifindex) int nmp_utils_ethtool_get_peer_ifindex (int ifindex) { + nm_auto_socket_handle SocketHandle shandle = { }; + int r; + gs_free struct ethtool_stats *stats = NULL; int peer_ifindex_stat; g_return_val_if_fail (ifindex > 0, 0); - peer_ifindex_stat = ethtool_get_stringset_index (ifindex, ETH_SS_STATS, "peer_ifindex"); - if (peer_ifindex_stat == -1) { - nm_log_dbg (LOGD_PLATFORM, "ethtool: peer_ifindex stat for %d does not exist?", ifindex); + if ((r = socket_handle_init (&shandle, ifindex)) < 0) { + nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s", + ifindex, + "get-peer-ifindex", + g_strerror (-r)); + return FALSE; + } + + peer_ifindex_stat = ethtool_get_stringset_index (&shandle, ETH_SS_STATS, "peer_ifindex"); + if (peer_ifindex_stat < 0) { + nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex); return FALSE; } stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64)); stats->cmd = ETHTOOL_GSTATS; stats->n_stats = peer_ifindex_stat + 1; - if (!ethtool_get (ifindex, stats)) + if (ethtool_call_ifindex (ifindex, stats) < 0) return 0; return stats->data[peer_ifindex_stat]; @@ -333,7 +832,7 @@ nmp_utils_ethtool_get_wake_on_lan (int ifindex) memset (&wol, 0, sizeof (wol)); wol.cmd = ETHTOOL_GWOL; - if (!ethtool_get (ifindex, &wol)) + if (ethtool_call_ifindex (ifindex, &wol) < 0) return FALSE; return wol.wolopts != 0; @@ -351,7 +850,7 @@ nmp_utils_ethtool_get_link_settings (int ifindex, g_return_val_if_fail (ifindex > 0, FALSE); - if (!ethtool_get (ifindex, &edata)) + if (ethtool_call_ifindex (ifindex, &edata) < 0) return FALSE; if (out_autoneg) @@ -433,9 +932,13 @@ nmp_utils_ethtool_set_link_settings (int ifindex, || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), FALSE); /* retrieve first current settings */ - if (!ethtool_get (ifindex, &edata)) + if (ethtool_call_ifindex (ifindex, &edata) < 0) return FALSE; + /* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3f1ac7a700d039c61d8d8b99f28d605d489a60cf + */ + /* then change the needed ones */ edata.cmd = ETHTOOL_SSET; if (autoneg) { @@ -485,7 +988,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex, } } - return ethtool_get (ifindex, &edata); + return ethtool_call_ifindex (ifindex, &edata) >= 0; } gboolean @@ -500,8 +1003,8 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex, if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE) return TRUE; - nm_log_dbg (LOGD_PLATFORM, "setting Wake-on-LAN options 0x%x, password '%s'", - (unsigned) wol, wol_password); + nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'", + ifindex, (unsigned) wol, wol_password); wol_info.cmd = ETHTOOL_SWOL; wol_info.wolopts = 0; @@ -521,45 +1024,41 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex, if (wol_password) { if (!nm_utils_hwaddr_aton (wol_password, wol_info.sopass, ETH_ALEN)) { - nm_log_dbg (LOGD_PLATFORM, "couldn't parse Wake-on-LAN password '%s'", wol_password); + nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'", ifindex, wol_password); return FALSE; } wol_info.wolopts |= WAKE_MAGICSECURE; } - return ethtool_get (ifindex, &wol_info); + return ethtool_call_ifindex (ifindex, &wol_info) >= 0; } -/****************************************************************** +/****************************************************************************** * mii - ******************************************************************/ + *****************************************************************************/ gboolean nmp_utils_mii_supports_carrier_detect (int ifindex) { - char ifname[IFNAMSIZ]; - nm_auto_close int fd = -1; + nm_auto_socket_handle SocketHandle shandle = { }; + int r; struct ifreq ifr; struct mii_ioctl_data *mii; g_return_val_if_fail (ifindex > 0, FALSE); - if (!nmp_utils_if_indextoname (ifindex, ifname)) { - nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: request fails resolving ifindex: %s", ifindex, g_strerror (errno)); - return FALSE; - } - - fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (fd < 0) { - nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: couldn't open control socket: %s", ifindex, ifname, g_strerror (errno)); + if ((r = socket_handle_init (&shandle, ifindex)) < 0) { + nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s", + ifindex, + g_strerror (-r)); return FALSE; } memset (&ifr, 0, sizeof (struct ifreq)); - memcpy (ifr.ifr_name, ifname, IFNAMSIZ); + memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ); - if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) { - nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, ifname, strerror (errno)); + if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) { + nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, strerror (errno)); return FALSE; } @@ -567,18 +1066,18 @@ nmp_utils_mii_supports_carrier_detect (int ifindex) mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; mii->reg_num = MII_BMSR; - if (ioctl (fd, SIOCGMIIREG, &ifr) != 0) { - nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, ifname, strerror (errno)); + if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) { + nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, strerror (errno)); return FALSE; } - nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, ifname, mii->val_out); + nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, shandle.ifname, mii->val_out); return TRUE; } -/****************************************************************** +/****************************************************************************** * udev - ******************************************************************/ + *****************************************************************************/ const char * nmp_utils_udev_get_driver (struct udev_device *udevice) |