VALID_IN4 = 1 << 2,
VALID_IN6 = 1 << 3,
VALID_MTU = 1 << 4,
- VALID_CARRIER = 1 << 5
+ VALID_CARRIER = 1 << 5,
+ VALID_IS_INTERNAL = 1 << 6
};
/* Cached network device information. */
int ifindex;
uint8_t etheraddr[ETH_ADDR_LEN];
- struct in_addr in4;
+ struct in_addr address, netmask;
struct in6_addr in6;
int mtu;
int carrier;
+ bool is_internal;
};
static struct shash cache_map = SHASH_INITIALIZER(&cache_map);
int cmd, const char *cmd_name);
static int netdev_linux_do_ioctl(const struct netdev *, struct ifreq *,
int cmd, const char *cmd_name);
+static int netdev_linux_get_ipv4(const struct netdev *, struct in_addr *,
+ int cmd, const char *cmd_name);
static int get_flags(const struct netdev *, int *flagsp);
static int set_flags(struct netdev *, int flags);
static int do_get_ifindex(const char *netdev_name);
* XXX All of the members of struct netdev_stats are 64 bits wide, but on
* 32-bit architectures the Linux network stats are only 32 bits. */
static int
-netdev_linux_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
+netdev_linux_get_stats(const struct netdev *netdev_, struct netdev_stats *stats)
{
+ struct netdev_linux *netdev = netdev_linux_cast(netdev_);
static int use_netlink_stats = -1;
int error;
+ struct netdev_stats raw_stats;
+ struct netdev_stats *collect_stats = stats;
COVERAGE_INC(netdev_get_stats);
+
+ if (!(netdev->cache->valid & VALID_IS_INTERNAL)) {
+ netdev->cache->is_internal = (netdev->tap_fd != -1);
+
+ if (!netdev->cache->is_internal) {
+ struct ethtool_drvinfo drvinfo;
+
+ memset(&drvinfo, 0, sizeof drvinfo);
+ error = netdev_linux_do_ethtool(&netdev->netdev,
+ (struct ethtool_cmd *)&drvinfo,
+ ETHTOOL_GDRVINFO,
+ "ETHTOOL_GDRVINFO");
+
+ if (!error) {
+ netdev->cache->is_internal = !strcmp(drvinfo.driver,
+ "openvswitch");
+ }
+ }
+
+ netdev->cache->valid |= VALID_IS_INTERNAL;
+ }
+
+ if (netdev->cache->is_internal) {
+ collect_stats = &raw_stats;
+ }
+
if (use_netlink_stats < 0) {
use_netlink_stats = check_for_working_netlink_stats();
}
if (use_netlink_stats) {
int ifindex;
- error = get_ifindex(netdev, &ifindex);
+ error = get_ifindex(&netdev->netdev, &ifindex);
if (!error) {
- error = get_stats_via_netlink(ifindex, stats);
+ error = get_stats_via_netlink(ifindex, collect_stats);
}
} else {
- error = get_stats_via_proc(netdev->name, stats);
+ error = get_stats_via_proc(netdev->netdev.name, collect_stats);
+ }
+
+ /* If this port is an internal port then the transmit and receive stats
+ * will appear to be swapped relative to the other ports since we are the
+ * one sending the data, not a remote computer. For consistency, we swap
+ * them back here. */
+ if (netdev->cache->is_internal) {
+ stats->rx_packets = raw_stats.tx_packets;
+ stats->tx_packets = raw_stats.rx_packets;
+ stats->rx_bytes = raw_stats.tx_bytes;
+ stats->tx_bytes = raw_stats.rx_bytes;
+ stats->rx_errors = raw_stats.tx_errors;
+ stats->tx_errors = raw_stats.rx_errors;
+ stats->rx_dropped = raw_stats.tx_dropped;
+ stats->tx_dropped = raw_stats.rx_dropped;
+ stats->multicast = raw_stats.multicast;
+ stats->collisions = raw_stats.collisions;
+ stats->rx_length_errors = 0;
+ stats->rx_over_errors = 0;
+ stats->rx_crc_errors = 0;
+ stats->rx_frame_errors = 0;
+ stats->rx_fifo_errors = 0;
+ stats->rx_missed_errors = 0;
+ stats->tx_aborted_errors = 0;
+ stats->tx_carrier_errors = 0;
+ stats->tx_fifo_errors = 0;
+ stats->tx_heartbeat_errors = 0;
+ stats->tx_window_errors = 0;
}
+
return error;
}
return 0;
}
-/* If 'netdev' has an assigned IPv4 address, sets '*in4' to that address (if
- * 'in4' is non-null) and returns true. Otherwise, returns false. */
static int
-netdev_linux_get_in4(const struct netdev *netdev_, struct in_addr *in4)
+netdev_linux_get_in4(const struct netdev *netdev_,
+ struct in_addr *address, struct in_addr *netmask)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
if (!(netdev->cache->valid & VALID_IN4)) {
- const struct sockaddr_in *sin;
- struct ifreq ifr;
int error;
- ifr.ifr_addr.sa_family = AF_INET;
- error = netdev_linux_do_ioctl(netdev_, &ifr,
+ error = netdev_linux_get_ipv4(netdev_, &netdev->cache->address,
SIOCGIFADDR, "SIOCGIFADDR");
if (error) {
return error;
}
- sin = (struct sockaddr_in *) &ifr.ifr_addr;
- netdev->cache->in4 = sin->sin_addr;
+ error = netdev_linux_get_ipv4(netdev_, &netdev->cache->netmask,
+ SIOCGIFNETMASK, "SIOCGIFNETMASK");
+ if (error) {
+ return error;
+ }
+
netdev->cache->valid |= VALID_IN4;
}
- *in4 = netdev->cache->in4;
- return in4->s_addr == INADDR_ANY ? EADDRNOTAVAIL : 0;
+ *address = netdev->cache->address;
+ *netmask = netdev->cache->netmask;
+ return address->s_addr == INADDR_ANY ? EADDRNOTAVAIL : 0;
}
-/* Assigns 'addr' as 'netdev''s IPv4 address and 'mask' as its netmask. If
- * 'addr' is INADDR_ANY, 'netdev''s IPv4 address is cleared. Returns a
- * positive errno value. */
static int
-netdev_linux_set_in4(struct netdev *netdev_, struct in_addr addr,
- struct in_addr mask)
+netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
+ struct in_addr netmask)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
int error;
- error = do_set_addr(netdev_, SIOCSIFADDR, "SIOCSIFADDR", addr);
+ error = do_set_addr(netdev_, SIOCSIFADDR, "SIOCSIFADDR", address);
if (!error) {
netdev->cache->valid |= VALID_IN4;
- netdev->cache->in4 = addr;
- if (addr.s_addr != INADDR_ANY) {
+ netdev->cache->address = address;
+ netdev->cache->netmask = netmask;
+ if (address.s_addr != INADDR_ANY) {
error = do_set_addr(netdev_, SIOCSIFNETMASK,
- "SIOCSIFNETMASK", mask);
+ "SIOCSIFNETMASK", netmask);
}
}
return error;
return error;
}
+static int
+netdev_linux_get_next_hop(const struct in_addr *host, struct in_addr *next_hop,
+ char **netdev_name)
+{
+ static const char fn[] = "/proc/net/route";
+ FILE *stream;
+ char line[256];
+ int ln;
+
+ *netdev_name = NULL;
+ stream = fopen(fn, "r");
+ if (stream == NULL) {
+ VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
+ return errno;
+ }
+
+ ln = 0;
+ while (fgets(line, sizeof line, stream)) {
+ if (++ln >= 2) {
+ char iface[17];
+ uint32_t dest, gateway, mask;
+ int refcnt, metric, mtu;
+ unsigned int flags, use, window, irtt;
+
+ if (sscanf(line,
+ "%16s %"SCNx32" %"SCNx32" %04X %d %u %d %"SCNx32
+ " %d %u %u\n",
+ iface, &dest, &gateway, &flags, &refcnt,
+ &use, &metric, &mask, &mtu, &window, &irtt) != 11) {
+
+ VLOG_WARN_RL(&rl, "%s: could not parse line %d: %s",
+ fn, ln, line);
+ continue;
+ }
+ if (!(flags & RTF_UP)) {
+ /* Skip routes that aren't up. */
+ continue;
+ }
+
+ /* The output of 'dest', 'mask', and 'gateway' were given in
+ * network byte order, so we don't need need any endian
+ * conversions here. */
+ if ((dest & mask) == (host->s_addr & mask)) {
+ if (!gateway) {
+ /* The host is directly reachable. */
+ next_hop->s_addr = 0;
+ } else {
+ /* To reach the host, we must go through a gateway. */
+ next_hop->s_addr = gateway;
+ }
+ *netdev_name = xstrdup(iface);
+ fclose(stream);
+ return 0;
+ }
+ }
+ }
+
+ fclose(stream);
+ return ENXIO;
+}
+
/* Looks up the ARP table entry for 'ip' on 'netdev'. If one exists and can be
* successfully retrieved, it stores the corresponding MAC address in 'mac' and
* returns 0. Otherwise, it returns a positive errno value; in particular,
netdev_linux_set_in4,
netdev_linux_get_in6,
netdev_linux_add_router,
+ netdev_linux_get_next_hop,
netdev_linux_arp_lookup,
netdev_linux_update_flags,
netdev_linux_set_in4,
netdev_linux_get_in6,
netdev_linux_add_router,
+ netdev_linux_get_next_hop,
netdev_linux_arp_lookup,
netdev_linux_update_flags,
}
return 0;
}
+
+static int
+netdev_linux_get_ipv4(const struct netdev *netdev, struct in_addr *ip,
+ int cmd, const char *cmd_name)
+{
+ struct ifreq ifr;
+ int error;
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ error = netdev_linux_do_ioctl(netdev, &ifr, cmd, cmd_name);
+ if (!error) {
+ const struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
+ *ip = sin->sin_addr;
+ }
+ return error;
+}