X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=lib%2Fnetdev-linux.c;h=49475360db0344bf834bd6847c9006a3280b7855;hb=a79c50f3df6197381c0ad8e91216ba2c5253473d;hp=85fcdf964c32252cb469f5490ca465b9a3bb1c9f;hpb=00fa9d37c2b2166358d9967a5fa03dc1ae45b2ca;p=openvswitch diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c index 85fcdf96..49475360 100644 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@ -24,11 +24,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -71,7 +73,6 @@ 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); @@ -112,9 +113,8 @@ enum { 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 { @@ -327,6 +327,9 @@ static unsigned int tc_buffer_per_jiffy(unsigned int rate); 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); @@ -365,7 +368,8 @@ struct netdev_dev_linux { 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; @@ -411,6 +415,7 @@ static int set_etheraddr(const char *netdev_name, int hwaddr_family, 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); @@ -501,6 +506,12 @@ netdev_linux_cache_cb(const struct rtnetlink_link_change *change, 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); } } @@ -511,20 +522,25 @@ netdev_linux_cache_cb(const struct rtnetlink_link_change *change, 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); @@ -537,9 +553,37 @@ netdev_linux_create(const struct netdev_class *class, const char *name, } 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; @@ -564,12 +608,17 @@ netdev_linux_create_tap(const struct netdev_class *class OVS_UNUSED, 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. */ @@ -579,19 +628,21 @@ netdev_linux_create_tap(const struct netdev_class *class OVS_UNUSED, 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; @@ -618,21 +669,12 @@ netdev_linux_destroy(struct netdev_dev *netdev_dev_) 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 @@ -694,28 +736,6 @@ netdev_linux_close(struct netdev *netdev_) 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_) { @@ -782,9 +802,9 @@ netdev_linux_recv(struct netdev *netdev_, void *data, size_t size) } for (;;) { - ssize_t retval = read(netdev->fd, data, size); + ssize_t retval = recv(netdev->fd, data, size, MSG_TRUNC); if (retval >= 0) { - return retval; + return retval <= size ? retval : -EMSGSIZE; } else if (errno != EINTR) { if (errno != EAGAIN) { VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s", @@ -1010,6 +1030,10 @@ netdev_linux_set_mtu(const struct netdev *netdev_, int mtu) struct ifreq ifr; int error; + if (netdev_dev->cache_valid & VALID_MTU && + netdev_dev->mtu == mtu) { + return 0; + } ifr.ifr_mtu = mtu; error = netdev_linux_do_ioctl(netdev_get_name(netdev_), &ifr, SIOCSIFMTU, "SIOCSIFMTU"); @@ -1038,62 +1062,20 @@ netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier) { 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 @@ -1266,8 +1248,8 @@ get_stats_via_vport(const struct netdev *netdev_, error = netdev_vport_get_stats(netdev_, stats); if (error) { - VLOG_WARN_RL(&rl, "%s: obtaining netdev stats via vport failed %d", - netdev_get_name(netdev_), error); + VLOG_WARN_RL(&rl, "%s: obtaining netdev stats via vport failed " + "(%s)", netdev_get_name(netdev_), strerror(error)); } netdev_dev->have_vport_stats = !error; netdev_dev->cache_valid |= VALID_HAVE_VPORT_STATS; @@ -1590,104 +1572,8 @@ netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise) 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) @@ -1695,7 +1581,7 @@ netdev_linux_set_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); - char command[1024]; + int error; COVERAGE_INC(netdev_set_policing); @@ -1710,27 +1596,34 @@ netdev_linux_set_policing(struct netdev *netdev, 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; } @@ -2340,7 +2233,7 @@ netdev_linux_change_seq(const struct netdev *netdev) 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, \ \ @@ -2356,8 +2249,6 @@ netdev_linux_change_seq(const struct netdev *netdev) netdev_linux_open, \ netdev_linux_close, \ \ - ENUMERATE, \ - \ netdev_linux_listen, \ netdev_linux_recv, \ netdev_linux_recv_wait, \ @@ -2372,13 +2263,13 @@ netdev_linux_change_seq(const struct netdev *netdev) 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, \ @@ -2409,7 +2300,6 @@ const struct netdev_class netdev_linux_class = NETDEV_LINUX_CLASS( "system", netdev_linux_create, - netdev_linux_enumerate, netdev_linux_get_stats, NULL); /* set_stats */ @@ -2417,7 +2307,6 @@ const struct netdev_class netdev_tap_class = NETDEV_LINUX_CLASS( "tap", netdev_linux_create_tap, - NULL, /* enumerate */ netdev_pseudo_get_stats, NULL); /* set_stats */ @@ -2425,7 +2314,6 @@ const struct netdev_class netdev_internal_class = NETDEV_LINUX_CLASS( "internal", netdev_linux_create, - NULL, /* enumerate */ netdev_pseudo_get_stats, netdev_vport_set_stats); @@ -3576,6 +3464,107 @@ tc_transact(struct ofpbuf *request, struct ofpbuf **replyp) 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 handle ffff: ingress + * + * This function is equivalent to running the following when 'add' is false: + * /sbin/tc qdisc del dev handle ffff: ingress + * + * The configuration and stats may be seen with the following command: + * /sbin/tc -s qdisc show dev + * + * 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 parent ffff: protocol all prio 49 + * basic police rate kbit burst k + * mtu 65535 drop + * + * The configuration and stats may be seen with the following command: + * /sbin/tc -s filter show 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) { @@ -4036,6 +4025,63 @@ tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes) return tc_bytes_to_ticks(Bps, MAX(burst_bytes, min_burst)); } +/* 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; +} + +/* Utility functions. */ + /* Copies 'src' into 'dst', performing format conversion in the process. */ static void netdev_stats_from_rtnl_link_stats(struct netdev_stats *dst, @@ -4064,9 +4110,6 @@ netdev_stats_from_rtnl_link_stats(struct netdev_stats *dst, dst->tx_window_errors = src->tx_window_errors; } - -/* Utility functions. */ - static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats) { @@ -4174,6 +4217,58 @@ get_stats_via_proc(const char *netdev_name, 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) { @@ -4301,51 +4396,6 @@ netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *ecmd, } } -/* 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)