#include <arpa/inet.h>
#include <inttypes.h>
#include <linux/gen_stats.h>
+#include <linux/if_ether.h>
#include <linux/if_tun.h>
#include <linux/ip.h>
#include <linux/types.h>
#include <linux/ethtool.h>
#include <linux/mii.h>
+#include <linux/pkt_cls.h>
#include <linux/pkt_sched.h>
#include <linux/rtnetlink.h>
#include <linux/sockios.h>
VLOG_DEFINE_THIS_MODULE(netdev_linux);
-COVERAGE_DEFINE(netdev_get_vlan_vid);
COVERAGE_DEFINE(netdev_set_policing);
COVERAGE_DEFINE(netdev_arp_lookup);
COVERAGE_DEFINE(netdev_get_ifindex);
VALID_IN4 = 1 << 2,
VALID_IN6 = 1 << 3,
VALID_MTU = 1 << 4,
- VALID_CARRIER = 1 << 5,
- VALID_POLICING = 1 << 6,
- VALID_HAVE_VPORT_STATS = 1 << 7
+ VALID_POLICING = 1 << 5,
+ VALID_HAVE_VPORT_STATS = 1 << 6
};
struct tap_state {
static struct tcmsg *tc_make_request(const struct netdev *, int type,
unsigned int flags, struct ofpbuf *);
static int tc_transact(struct ofpbuf *request, struct ofpbuf **replyp);
+static int tc_add_del_ingress_qdisc(struct netdev *netdev, bool add);
+static int tc_add_policer(struct netdev *netdev, int kbits_rate,
+ int kbits_burst);
static int tc_parse_qdisc(const struct ofpbuf *, const char **kind,
struct nlattr **options);
struct in_addr address, netmask;
struct in6_addr in6;
int mtu;
- int carrier;
+ bool carrier;
+ long long int carrier_resets;
uint32_t kbits_rate; /* Policing data. */
uint32_t kbits_burst;
bool have_vport_stats;
const uint8_t[ETH_ADDR_LEN]);
static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats);
static int get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats);
+static int get_carrier_via_sysfs(const char *name, bool *carrier);
static int af_packet_sock(void);
static void netdev_linux_miimon_run(void);
static void netdev_linux_miimon_wait(void);
if (is_netdev_linux_class(netdev_class)) {
dev = netdev_dev_linux_cast(base_dev);
+
+ if (dev->carrier != change->running) {
+ dev->carrier = change->running;
+ dev->carrier_resets++;
+ }
+
netdev_dev_linux_changed(dev);
}
}
shash_init(&device_shash);
netdev_dev_get_devices(&netdev_linux_class, &device_shash);
SHASH_FOR_EACH (node, &device_shash) {
+ bool carrier;
+
dev = node->data;
+
+ get_carrier_via_sysfs(node->name, &carrier);
+ if (dev->carrier != carrier) {
+ dev->carrier = carrier;
+ dev->carrier_resets++;
+ }
+
netdev_dev_linux_changed(dev);
}
shash_destroy(&device_shash);
}
}
-/* Creates system and internal devices. */
static int
-netdev_linux_create(const struct netdev_class *class, const char *name,
- struct netdev_dev **netdev_devp)
+cache_notifier_ref(void)
{
- struct netdev_dev_linux *netdev_dev;
-
if (!cache_notifier_refcount) {
assert(!netdev_linux_cache_notifier);
}
cache_notifier_refcount++;
+ return 0;
+}
+
+static void
+cache_notifier_unref(void)
+{
+ assert(cache_notifier_refcount > 0);
+ if (!--cache_notifier_refcount) {
+ assert(netdev_linux_cache_notifier);
+ rtnetlink_link_notifier_destroy(netdev_linux_cache_notifier);
+ netdev_linux_cache_notifier = NULL;
+ }
+}
+
+/* Creates system and internal devices. */
+static int
+netdev_linux_create(const struct netdev_class *class, const char *name,
+ struct netdev_dev **netdev_devp)
+{
+ struct netdev_dev_linux *netdev_dev;
+ int error;
+
+ error = cache_notifier_ref();
+ if (error) {
+ return error;
+ }
+
netdev_dev = xzalloc(sizeof *netdev_dev);
netdev_dev->change_seq = 1;
netdev_dev_init(&netdev_dev->netdev_dev, name, class);
+ get_carrier_via_sysfs(name, &netdev_dev->carrier);
*netdev_devp = &netdev_dev->netdev_dev;
return 0;
netdev_dev = xzalloc(sizeof *netdev_dev);
state = &netdev_dev->state.tap;
+ error = cache_notifier_ref();
+ if (error) {
+ goto error;
+ }
+
/* Open tap device. */
state->fd = open(tap_dev, O_RDWR);
if (state->fd < 0) {
error = errno;
VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
- goto error;
+ goto error_unref_notifier;
}
/* Create tap device. */
VLOG_WARN("%s: creating tap device failed: %s", name,
strerror(errno));
error = errno;
- goto error;
+ goto error_unref_notifier;
}
/* Make non-blocking. */
error = set_nonblocking(state->fd);
if (error) {
- goto error;
+ goto error_unref_notifier;
}
netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_tap_class);
*netdev_devp = &netdev_dev->netdev_dev;
return 0;
+error_unref_notifier:
+ cache_notifier_unref();
error:
free(netdev_dev);
return error;
netdev_dev->tc->ops->tc_destroy(netdev_dev->tc);
}
- if (class == &netdev_linux_class || class == &netdev_internal_class) {
- cache_notifier_refcount--;
-
- if (!cache_notifier_refcount) {
- assert(netdev_linux_cache_notifier);
- rtnetlink_link_notifier_destroy(netdev_linux_cache_notifier);
- netdev_linux_cache_notifier = NULL;
- }
- } else if (class == &netdev_tap_class) {
+ if (class == &netdev_tap_class) {
destroy_tap(netdev_dev);
- } else {
- NOT_REACHED();
}
-
free(netdev_dev);
+
+ cache_notifier_unref();
}
static int
free(netdev);
}
-/* Initializes 'sset' with a list of the names of all known network devices. */
-static int
-netdev_linux_enumerate(struct sset *sset)
-{
- struct if_nameindex *names;
-
- names = if_nameindex();
- if (names) {
- size_t i;
-
- for (i = 0; names[i].if_name != NULL; i++) {
- sset_add(sset, names[i].if_name);
- }
- if_freenameindex(names);
- return 0;
- } else {
- VLOG_WARN("could not obtain list of network device names: %s",
- strerror(errno));
- return errno;
- }
-}
-
static int
netdev_linux_listen(struct netdev *netdev_)
{
{
struct netdev_dev_linux *netdev_dev =
netdev_dev_linux_cast(netdev_get_dev(netdev_));
- int error = 0;
- char *fn = NULL;
- int fd = -1;
if (netdev_dev->miimon_interval > 0) {
*carrier = netdev_dev->miimon;
- return 0;
+ } else {
+ *carrier = netdev_dev->carrier;
}
- if (!(netdev_dev->cache_valid & VALID_CARRIER)) {
- char line[8];
- int retval;
-
- fn = xasprintf("/sys/class/net/%s/carrier",
- netdev_get_name(netdev_));
- fd = open(fn, O_RDONLY);
- if (fd < 0) {
- error = errno;
- VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(error));
- goto exit;
- }
-
- retval = read(fd, line, sizeof line);
- if (retval < 0) {
- error = errno;
- if (error == EINVAL) {
- /* This is the normal return value when we try to check carrier
- * if the network device is not up. */
- } else {
- VLOG_WARN_RL(&rl, "%s: read failed: %s", fn, strerror(error));
- }
- goto exit;
- } else if (retval == 0) {
- error = EPROTO;
- VLOG_WARN_RL(&rl, "%s: unexpected end of file", fn);
- goto exit;
- }
-
- if (line[0] != '0' && line[0] != '1') {
- error = EPROTO;
- VLOG_WARN_RL(&rl, "%s: value is %c (expected 0 or 1)",
- fn, line[0]);
- goto exit;
- }
- netdev_dev->carrier = line[0] != '0';
- netdev_dev->cache_valid |= VALID_CARRIER;
- }
- *carrier = netdev_dev->carrier;
- error = 0;
+ return 0;
+}
-exit:
- if (fd >= 0) {
- close(fd);
- }
- free(fn);
- return error;
+static long long int
+netdev_linux_get_carrier_resets(const struct netdev *netdev)
+{
+ return netdev_dev_linux_cast(netdev_get_dev(netdev))->carrier_resets;
}
static int
ETHTOOL_SSET, "ETHTOOL_SSET");
}
-/* If 'netdev_name' is the name of a VLAN network device (e.g. one created with
- * vconfig(8)), sets '*vlan_vid' to the VLAN VID associated with that device
- * and returns 0. Otherwise returns a errno value (specifically ENOENT if
- * 'netdev_name' is the name of a network device that is not a VLAN device) and
- * sets '*vlan_vid' to -1. */
-static int
-netdev_linux_get_vlan_vid(const struct netdev *netdev, int *vlan_vid)
-{
- const char *netdev_name = netdev_get_name(netdev);
- struct ds line = DS_EMPTY_INITIALIZER;
- FILE *stream = NULL;
- int error;
- char *fn;
-
- COVERAGE_INC(netdev_get_vlan_vid);
- fn = xasprintf("/proc/net/vlan/%s", netdev_name);
- stream = fopen(fn, "r");
- if (!stream) {
- error = errno;
- goto done;
- }
-
- if (ds_get_line(&line, stream)) {
- if (ferror(stream)) {
- error = errno;
- VLOG_ERR_RL(&rl, "error reading \"%s\": %s", fn, strerror(errno));
- } else {
- error = EPROTO;
- VLOG_ERR_RL(&rl, "unexpected end of file reading \"%s\"", fn);
- }
- goto done;
- }
-
- if (!sscanf(ds_cstr(&line), "%*s VID: %d", vlan_vid)) {
- error = EPROTO;
- VLOG_ERR_RL(&rl, "parse error reading \"%s\" line 1: \"%s\"",
- fn, ds_cstr(&line));
- goto done;
- }
-
- error = 0;
-
-done:
- free(fn);
- if (stream) {
- fclose(stream);
- }
- ds_destroy(&line);
- if (error) {
- *vlan_vid = -1;
- }
- return error;
-}
-
-#define POLICE_ADD_CMD "/sbin/tc qdisc add dev %s handle ffff: ingress"
-#define POLICE_CONFIG_CMD "/sbin/tc filter add dev %s parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate %dkbit burst %dk mtu 65535 drop flowid :1"
-
-/* Remove ingress policing from 'netdev'. Returns 0 if successful, otherwise a
- * positive errno value.
- *
- * This function is equivalent to running
- * /sbin/tc qdisc del dev %s handle ffff: ingress
- * but it is much, much faster.
- */
-static int
-netdev_linux_remove_policing(struct netdev *netdev)
-{
- struct netdev_dev_linux *netdev_dev =
- netdev_dev_linux_cast(netdev_get_dev(netdev));
- const char *netdev_name = netdev_get_name(netdev);
-
- struct ofpbuf request;
- struct tcmsg *tcmsg;
- int error;
-
- tcmsg = tc_make_request(netdev, RTM_DELQDISC, 0, &request);
- if (!tcmsg) {
- return ENODEV;
- }
- tcmsg->tcm_handle = tc_make_handle(0xffff, 0);
- tcmsg->tcm_parent = TC_H_INGRESS;
- nl_msg_put_string(&request, TCA_KIND, "ingress");
- nl_msg_put_unspec(&request, TCA_OPTIONS, NULL, 0);
-
- error = tc_transact(&request, NULL);
- if (error && error != ENOENT && error != EINVAL) {
- VLOG_WARN_RL(&rl, "%s: removing policing failed: %s",
- netdev_name, strerror(error));
- return error;
- }
-
- netdev_dev->kbits_rate = 0;
- netdev_dev->kbits_burst = 0;
- netdev_dev->cache_valid |= VALID_POLICING;
- return 0;
-}
-
-/* Attempts to set input rate limiting (policing) policy. */
+/* Attempts to set input rate limiting (policing) policy. Returns 0 if
+ * successful, otherwise a positive errno value. */
static int
netdev_linux_set_policing(struct netdev *netdev,
uint32_t kbits_rate, uint32_t kbits_burst)
struct netdev_dev_linux *netdev_dev =
netdev_dev_linux_cast(netdev_get_dev(netdev));
const char *netdev_name = netdev_get_name(netdev);
- char command[1024];
+ int error;
COVERAGE_INC(netdev_set_policing);
return 0;
}
- netdev_linux_remove_policing(netdev);
+ /* Remove any existing ingress qdisc. */
+ error = tc_add_del_ingress_qdisc(netdev, false);
+ if (error) {
+ VLOG_WARN_RL(&rl, "%s: removing policing failed: %s",
+ netdev_name, strerror(error));
+ return error;
+ }
+
if (kbits_rate) {
- snprintf(command, sizeof(command), POLICE_ADD_CMD, netdev_name);
- if (system(command) != 0) {
- VLOG_WARN_RL(&rl, "%s: problem adding policing", netdev_name);
- return -1;
+ error = tc_add_del_ingress_qdisc(netdev, true);
+ if (error) {
+ VLOG_WARN_RL(&rl, "%s: adding policing qdisc failed: %s",
+ netdev_name, strerror(error));
+ return error;
}
- snprintf(command, sizeof(command), POLICE_CONFIG_CMD, netdev_name,
- kbits_rate, kbits_burst);
- if (system(command) != 0) {
- VLOG_WARN_RL(&rl, "%s: problem configuring policing",
- netdev_name);
- return -1;
+ error = tc_add_policer(netdev, kbits_rate, kbits_burst);
+ if (error){
+ VLOG_WARN_RL(&rl, "%s: adding policing action failed: %s",
+ netdev_name, strerror(error));
+ return error;
}
-
- netdev_dev->kbits_rate = kbits_rate;
- netdev_dev->kbits_burst = kbits_burst;
- netdev_dev->cache_valid |= VALID_POLICING;
}
+ netdev_dev->kbits_rate = kbits_rate;
+ netdev_dev->kbits_burst = kbits_burst;
+ netdev_dev->cache_valid |= VALID_POLICING;
+
return 0;
}
return netdev_dev_linux_cast(netdev_get_dev(netdev))->change_seq;
}
-#define NETDEV_LINUX_CLASS(NAME, CREATE, ENUMERATE, GET_STATS, SET_STATS) \
+#define NETDEV_LINUX_CLASS(NAME, CREATE, GET_STATS, SET_STATS) \
{ \
NAME, \
\
netdev_linux_open, \
netdev_linux_close, \
\
- ENUMERATE, \
- \
netdev_linux_listen, \
netdev_linux_recv, \
netdev_linux_recv_wait, \
netdev_linux_set_mtu, \
netdev_linux_get_ifindex, \
netdev_linux_get_carrier, \
+ netdev_linux_get_carrier_resets, \
netdev_linux_set_miimon_interval, \
GET_STATS, \
SET_STATS, \
\
netdev_linux_get_features, \
netdev_linux_set_advertisements, \
- netdev_linux_get_vlan_vid, \
\
netdev_linux_set_policing, \
netdev_linux_get_qos_types, \
NETDEV_LINUX_CLASS(
"system",
netdev_linux_create,
- netdev_linux_enumerate,
netdev_linux_get_stats,
NULL); /* set_stats */
NETDEV_LINUX_CLASS(
"tap",
netdev_linux_create_tap,
- NULL, /* enumerate */
netdev_pseudo_get_stats,
NULL); /* set_stats */
NETDEV_LINUX_CLASS(
"internal",
netdev_linux_create,
- NULL, /* enumerate */
netdev_pseudo_get_stats,
netdev_vport_set_stats);
\f
return error;
}
+/* Adds or deletes a root ingress qdisc on 'netdev'. We use this for
+ * policing configuration.
+ *
+ * This function is equivalent to running the following when 'add' is true:
+ * /sbin/tc qdisc add dev <devname> handle ffff: ingress
+ *
+ * This function is equivalent to running the following when 'add' is false:
+ * /sbin/tc qdisc del dev <devname> handle ffff: ingress
+ *
+ * The configuration and stats may be seen with the following command:
+ * /sbin/tc -s qdisc show dev <devname>
+ *
+ * Returns 0 if successful, otherwise a positive errno value.
+ */
+static int
+tc_add_del_ingress_qdisc(struct netdev *netdev, bool add)
+{
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+ int type = add ? RTM_NEWQDISC : RTM_DELQDISC;
+ int flags = add ? NLM_F_EXCL | NLM_F_CREATE : 0;
+
+ tcmsg = tc_make_request(netdev, type, flags, &request);
+ if (!tcmsg) {
+ return ENODEV;
+ }
+ tcmsg->tcm_handle = tc_make_handle(0xffff, 0);
+ tcmsg->tcm_parent = TC_H_INGRESS;
+ nl_msg_put_string(&request, TCA_KIND, "ingress");
+ nl_msg_put_unspec(&request, TCA_OPTIONS, NULL, 0);
+
+ error = tc_transact(&request, NULL);
+ if (error) {
+ /* If we're deleting the qdisc, don't worry about some of the
+ * error conditions. */
+ if (!add && (error == ENOENT || error == EINVAL)) {
+ return 0;
+ }
+ return error;
+ }
+
+ return 0;
+}
+
+/* Adds a policer to 'netdev' with a rate of 'kbits_rate' and a burst size
+ * of 'kbits_burst'.
+ *
+ * This function is equivalent to running:
+ * /sbin/tc filter add dev <devname> parent ffff: protocol all prio 49
+ * basic police rate <kbits_rate>kbit burst <kbits_burst>k
+ * mtu 65535 drop
+ *
+ * The configuration and stats may be seen with the following command:
+ * /sbin/tc -s filter show <devname> eth0 parent ffff:
+ *
+ * Returns 0 if successful, otherwise a positive errno value.
+ */
+static int
+tc_add_policer(struct netdev *netdev, int kbits_rate, int kbits_burst)
+{
+ struct tc_police tc_police;
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ size_t basic_offset;
+ size_t police_offset;
+ int error;
+ int mtu = 65535;
+
+ memset(&tc_police, 0, sizeof tc_police);
+ tc_police.action = TC_POLICE_SHOT;
+ tc_police.mtu = mtu;
+ tc_fill_rate(&tc_police.rate, kbits_rate/8 * 1000, mtu);
+ tc_police.burst = tc_bytes_to_ticks(tc_police.rate.rate,
+ kbits_burst * 1024);
+
+ tcmsg = tc_make_request(netdev, RTM_NEWTFILTER,
+ NLM_F_EXCL | NLM_F_CREATE, &request);
+ if (!tcmsg) {
+ return ENODEV;
+ }
+ tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+ tcmsg->tcm_info = tc_make_handle(49,
+ (OVS_FORCE uint16_t) htons(ETH_P_ALL));
+
+ nl_msg_put_string(&request, TCA_KIND, "basic");
+ basic_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
+ police_offset = nl_msg_start_nested(&request, TCA_BASIC_POLICE);
+ nl_msg_put_unspec(&request, TCA_POLICE_TBF, &tc_police, sizeof tc_police);
+ tc_put_rtab(&request, TCA_POLICE_RATE, &tc_police.rate);
+ nl_msg_end_nested(&request, police_offset);
+ nl_msg_end_nested(&request, basic_offset);
+
+ error = tc_transact(&request, NULL);
+ if (error) {
+ return error;
+ }
+
+ return 0;
+}
+
static void
read_psched(void)
{
return tc_bytes_to_ticks(Bps, MAX(burst_bytes, min_burst));
}
\f
+/* Linux-only functions declared in netdev-linux.h */
+
+/* Returns a fd for an AF_INET socket or a negative errno value. */
+int
+netdev_linux_get_af_inet_sock(void)
+{
+ int error = netdev_linux_init();
+ return error ? -error : af_inet_sock;
+}
+
+/* Modifies the 'flag' bit in ethtool's flags field for 'netdev'. If
+ * 'enable' is true, the bit is set. Otherwise, it is cleared. */
+int
+netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
+ const char *flag_name, bool enable)
+{
+ const char *netdev_name = netdev_get_name(netdev);
+ struct ethtool_value evalue;
+ uint32_t new_flags;
+ int error;
+
+ memset(&evalue, 0, sizeof evalue);
+ error = netdev_linux_do_ethtool(netdev_name,
+ (struct ethtool_cmd *)&evalue,
+ ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
+ if (error) {
+ return error;
+ }
+
+ evalue.data = new_flags = (evalue.data & ~flag) | (enable ? flag : 0);
+ error = netdev_linux_do_ethtool(netdev_name,
+ (struct ethtool_cmd *)&evalue,
+ ETHTOOL_SFLAGS, "ETHTOOL_SFLAGS");
+ if (error) {
+ return error;
+ }
+
+ memset(&evalue, 0, sizeof evalue);
+ error = netdev_linux_do_ethtool(netdev_name,
+ (struct ethtool_cmd *)&evalue,
+ ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
+ if (error) {
+ return error;
+ }
+
+ if (new_flags != evalue.data) {
+ VLOG_WARN_RL(&rl, "attempt to %s ethtool %s flag on network "
+ "device %s failed", enable ? "enable" : "disable",
+ flag_name, netdev_name);
+ return EOPNOTSUPP;
+ }
+
+ return 0;
+}
+\f
+/* Utility functions. */
+
/* Copies 'src' into 'dst', performing format conversion in the process. */
static void
netdev_stats_from_rtnl_link_stats(struct netdev_stats *dst,
dst->tx_window_errors = src->tx_window_errors;
}
-\f
-/* Utility functions. */
-
static int
get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
{
return ENODEV;
}
+static int
+get_carrier_via_sysfs(const char *name, bool *carrier)
+{
+ char line[8];
+ int retval;
+
+ int error = 0;
+ char *fn = NULL;
+ int fd = -1;
+
+ *carrier = false;
+
+ fn = xasprintf("/sys/class/net/%s/carrier", name);
+ fd = open(fn, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(error));
+ goto exit;
+ }
+
+ retval = read(fd, line, sizeof line);
+ if (retval < 0) {
+ error = errno;
+ if (error == EINVAL) {
+ /* This is the normal return value when we try to check carrier if
+ * the network device is not up. */
+ } else {
+ VLOG_WARN_RL(&rl, "%s: read failed: %s", fn, strerror(error));
+ }
+ goto exit;
+ } else if (retval == 0) {
+ error = EPROTO;
+ VLOG_WARN_RL(&rl, "%s: unexpected end of file", fn);
+ goto exit;
+ }
+
+ if (line[0] != '0' && line[0] != '1') {
+ error = EPROTO;
+ VLOG_WARN_RL(&rl, "%s: value is %c (expected 0 or 1)", fn, line[0]);
+ goto exit;
+ }
+ *carrier = line[0] != '0';
+ error = 0;
+
+exit:
+ if (fd >= 0) {
+ close(fd);
+ }
+ free(fn);
+ return error;
+}
+
static int
get_flags(const struct netdev *netdev, int *flags)
{
}
}
-/* Modifies the 'flag' bit in ethtool's flags field for 'netdev'. If
- * 'enable' is true, the bit is set. Otherwise, it is cleared. */
-int
-netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
- const char *flag_name, bool enable)
-{
- const char *netdev_name = netdev_get_name(netdev);
- struct ethtool_value evalue;
- uint32_t new_flags;
- int error;
-
- memset(&evalue, 0, sizeof evalue);
- error = netdev_linux_do_ethtool(netdev_name,
- (struct ethtool_cmd *)&evalue,
- ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
- if (error) {
- return error;
- }
-
- evalue.data = new_flags = (evalue.data & ~flag) | (enable ? flag : 0);
- error = netdev_linux_do_ethtool(netdev_name,
- (struct ethtool_cmd *)&evalue,
- ETHTOOL_SFLAGS, "ETHTOOL_SFLAGS");
- if (error) {
- return error;
- }
-
- memset(&evalue, 0, sizeof evalue);
- error = netdev_linux_do_ethtool(netdev_name,
- (struct ethtool_cmd *)&evalue,
- ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
- if (error) {
- return error;
- }
-
- if (new_flags != evalue.data) {
- VLOG_WARN_RL(&rl, "attempt to %s ethtool %s flag on network "
- "device %s failed", enable ? "enable" : "disable",
- flag_name, netdev_name);
- return EOPNOTSUPP;
- }
-
- return 0;
-}
-
static int
netdev_linux_do_ioctl(const char *name, struct ifreq *ifr, int cmd,
const char *cmd_name)