*/
#include <config.h>
-
-#include "netdev-linux.h"
-
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include "openflow/openflow.h"
#include "packets.h"
#include "poll-loop.h"
+#include "rtnetlink.h"
#include "socket-util.h"
#include "shash.h"
#include "svec.h"
#define ADVERTISED_Asym_Pause (1 << 14)
#endif
+/* Provider-specific netdev object. Netdev objects are devices that are
+ * created by the netdev library through a netdev_create() call. */
+struct netdev_obj_linux {
+ struct netdev_obj netdev_obj;
+
+ int tap_fd; /* File descriptor for TAP device. */
+};
+
struct netdev_linux {
struct netdev netdev;
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);
static struct rtnetlink_notifier netdev_linux_cache_notifier;
-/* Policy for RTNLGRP_LINK messages.
- *
- * There are *many* more fields in these messages, but currently we only care
- * about interface names. */
-static const struct nl_policy rtnlgrp_link_policy[] = {
- [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
- [IFLA_MASTER] = { .type = NL_A_U32, .optional = true },
- [IFLA_STATS] = { .type = NL_A_UNSPEC, .optional = true,
- .min_len = sizeof(struct rtnl_link_stats) },
-};
-
/* An AF_INET socket (used for ioctl operations). */
static int af_inet_sock = -1;
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);
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 struct netdev_obj_linux *
+netdev_obj_linux_cast(const struct netdev_obj *netdev_obj)
+{
+ netdev_obj_assert_class(netdev_obj, &netdev_linux_class);
+ return CONTAINER_OF(netdev_obj, struct netdev_obj_linux, netdev_obj);
+}
+
static struct netdev_linux *
netdev_linux_cast(const struct netdev *netdev)
{
}
}
+/* Creates the netdev object of 'type' with 'name'. */
static int
-netdev_linux_open(const char *name, char *suffix, int ethertype,
- struct netdev **netdevp)
+netdev_linux_create(const char *name, const char *type,
+ const struct shash *args, bool created)
+{
+ struct netdev_obj_linux *netdev_obj;
+ static const char tap_dev[] = "/dev/net/tun";
+ struct ifreq ifr;
+ int error;
+
+ if (!shash_is_empty(args)) {
+ VLOG_WARN("arguments for %s devices should be empty", type);
+ }
+
+ /* Create the name binding in the netdev library for this object. */
+ netdev_obj = xcalloc(1, sizeof *netdev_obj);
+ netdev_obj_init(&netdev_obj->netdev_obj, name, &netdev_linux_class,
+ created);
+ netdev_obj->tap_fd = -1;
+
+ if (strcmp(type, "tap")) {
+ return 0;
+ }
+
+ /* Open tap device. */
+ netdev_obj->tap_fd = open(tap_dev, O_RDWR);
+ if (netdev_obj->tap_fd < 0) {
+ error = errno;
+ VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
+ goto error;
+ }
+
+ /* Create tap device. */
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+ strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+ if (ioctl(netdev_obj->tap_fd, TUNSETIFF, &ifr) == -1) {
+ VLOG_WARN("%s: creating tap device failed: %s", name,
+ strerror(errno));
+ error = errno;
+ goto error;
+ }
+
+ /* Make non-blocking. */
+ error = set_nonblocking(netdev_obj->tap_fd);
+ if (error) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ netdev_destroy(name);
+ return error;
+}
+
+/* Destroys the netdev object 'netdev_obj_'. */
+static void
+netdev_linux_destroy(struct netdev_obj *netdev_obj_)
+{
+ struct netdev_obj_linux *netdev_obj = netdev_obj_linux_cast(netdev_obj_);
+
+ if (netdev_obj->tap_fd >= 0) {
+ close(netdev_obj->tap_fd);
+ }
+ free(netdev_obj);
+
+ return;
+}
+
+static int
+netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
{
struct netdev_linux *netdev;
enum netdev_flags flags;
/* Allocate network device. */
netdev = xcalloc(1, sizeof *netdev);
- netdev_init(&netdev->netdev, suffix, &netdev_linux_class);
+ netdev_init(&netdev->netdev, name, &netdev_linux_class);
netdev->netdev_fd = -1;
netdev->tap_fd = -1;
- netdev->cache = shash_find_data(&cache_map, suffix);
+ netdev->cache = shash_find_data(&cache_map, name);
if (!netdev->cache) {
if (shash_is_empty(&cache_map)) {
int error = rtnetlink_notifier_register(
}
}
netdev->cache = xmalloc(sizeof *netdev->cache);
- netdev->cache->shash_node = shash_add(&cache_map, suffix,
+ netdev->cache->shash_node = shash_add(&cache_map, name,
netdev->cache);
netdev->cache->valid = 0;
netdev->cache->ref_cnt = 0;
}
netdev->cache->ref_cnt++;
- if (!strncmp(name, "tap:", 4)) {
+ if (!strcmp(netdev_get_type(&netdev->netdev), "tap")) {
static const char tap_dev[] = "/dev/net/tun";
struct ifreq ifr;
/* Create tap device. */
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
- error = netdev_linux_do_ioctl(&netdev->netdev, &ifr,
- TUNSETIFF, "TUNSETIFF");
- if (error) {
+ strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+ if (ioctl(netdev->tap_fd, TUNSETIFF, &ifr) == -1) {
+ VLOG_WARN("%s: creating tap device failed: %s", name,
+ strerror(errno));
+ error = errno;
goto error;
}
if (bind(netdev->netdev_fd,
(struct sockaddr *) &sll, sizeof sll) < 0) {
error = errno;
- VLOG_ERR("bind to %s failed: %s", suffix, strerror(error));
+ VLOG_ERR("bind to %s failed: %s", name, strerror(error));
goto error;
}
if (netdev->tap_fd < 0) {
/* Device was opened with NETDEV_ETH_TYPE_NONE. */
- return EAGAIN;
+ return -EAGAIN;
}
for (;;) {
VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s",
strerror(errno), netdev_get_name(netdev_));
}
- return errno;
+ return -errno;
}
}
}
const uint8_t mac[ETH_ADDR_LEN])
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
- int error = set_etheraddr(netdev_get_name(netdev_), ARPHRD_ETHER, mac);
- if (!error) {
- memcpy(netdev->cache->etheraddr, mac, ETH_ADDR_LEN);
+ int error;
+
+ if (!(netdev->cache->valid & VALID_ETHERADDR)
+ || !eth_addr_equals(netdev->cache->etheraddr, mac)) {
+ error = set_etheraddr(netdev_get_name(netdev_), ARPHRD_ETHER, mac);
+ if (!error) {
+ netdev->cache->valid |= VALID_ETHERADDR;
+ memcpy(netdev->cache->etheraddr, mac, ETH_ADDR_LEN);
+ }
+ } else {
+ error = 0;
}
return error;
}
return 0;
}
+/* Returns the ifindex of 'netdev', if successful, as a positive number.
+ * On failure, returns a negative errno value. */
+static int
+netdev_linux_get_ifindex(const struct netdev *netdev)
+{
+ int ifindex, error;
+
+ error = get_ifindex(netdev, &ifindex);
+ return error ? -error : ifindex;
+}
+
static int
netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
{
* 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;
}
/* Stores the features supported by 'netdev' into each of '*current',
* '*advertised', '*supported', and '*peer' that are non-null. Each value is a
* bitmap of "enum ofp_port_features" bits, in host byte order. Returns 0 if
- * successful, otherwise a positive errno value. On failure, all of the
- * passed-in values are set to 0. */
+ * successful, otherwise a positive errno value. */
static int
netdev_linux_get_features(struct netdev *netdev,
uint32_t *current, uint32_t *advertised,
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,
}
const struct netdev_class netdev_linux_class = {
- "", /* prefix */
- "linux", /* name */
+ "system", /* type */
netdev_linux_init,
netdev_linux_run,
netdev_linux_wait,
+ netdev_linux_create,
+ netdev_linux_destroy,
+ NULL, /* reconfigure */
+
netdev_linux_open,
netdev_linux_close,
netdev_linux_set_etheraddr,
netdev_linux_get_etheraddr,
netdev_linux_get_mtu,
+ netdev_linux_get_ifindex,
netdev_linux_get_carrier,
netdev_linux_get_stats,
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,
};
const struct netdev_class netdev_tap_class = {
- "tap", /* prefix */
- "tap", /* name */
+ "tap", /* type */
netdev_linux_init,
NULL, /* run */
NULL, /* wait */
+ netdev_linux_create,
+ netdev_linux_destroy,
+ NULL, /* reconfigure */
+
netdev_linux_open,
netdev_linux_close,
netdev_linux_set_etheraddr,
netdev_linux_get_etheraddr,
netdev_linux_get_mtu,
+ netdev_linux_get_ifindex,
netdev_linux_get_carrier,
netdev_linux_get_stats,
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,
static int
get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
{
+ /* Policy for RTNLGRP_LINK messages.
+ *
+ * There are *many* more fields in these messages, but currently we only
+ * care about these fields. */
+ static const struct nl_policy rtnlgrp_link_policy[] = {
+ [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
+ [IFLA_STATS] = { .type = NL_A_UNSPEC, .optional = true,
+ .min_len = sizeof(struct rtnl_link_stats) },
+ };
+
+
static struct nl_sock *rtnl_sock;
struct ofpbuf request;
struct ofpbuf *reply;
if (!attrs[IFLA_STATS]) {
VLOG_WARN_RL(&rl, "RTM_GETLINK reply lacks stats");
+ ofpbuf_delete(reply);
return EPROTO;
}
stats->tx_heartbeat_errors = rtnl_stats->tx_heartbeat_errors;
stats->tx_window_errors = rtnl_stats->tx_window_errors;
+ ofpbuf_delete(reply);
+
return 0;
}
}
return 0;
}
-\f
-/* rtnetlink socket. */
-static struct nl_sock *notify_sock;
-
-/* All registered notifiers. */
-static struct list all_notifiers = LIST_INITIALIZER(&all_notifiers);
-static void rtnetlink_report_change(const struct nlmsghdr *,
- const struct ifinfomsg *,
- struct nlattr *attrs[]);
-static void rtnetlink_report_notify_error(void);
-
-/* Registers 'cb' to be called with auxiliary data 'aux' with network device
- * change notifications. The notifier is stored in 'notifier', which the
- * caller must not modify or free.
- *
- * This is probably not the function that you want. You should probably be
- * using dpif_port_poll() or netdev_monitor_create(), which unlike this
- * function are not Linux-specific.
- *
- * Returns 0 if successful, otherwise a positive errno value. */
-int
-rtnetlink_notifier_register(struct rtnetlink_notifier *notifier,
- rtnetlink_notify_func *cb, void *aux)
-{
- if (!notify_sock) {
- int error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0,
- ¬ify_sock);
- if (error) {
- VLOG_WARN("could not create rtnetlink socket: %s",
- strerror(error));
- return error;
- }
- } else {
- /* Catch up on notification work so that the new notifier won't
- * receive any stale notifications. */
- rtnetlink_notifier_run();
- }
-
- list_push_back(&all_notifiers, ¬ifier->node);
- notifier->cb = cb;
- notifier->aux = aux;
- return 0;
-}
-
-/* Cancels notification on 'notifier', which must have previously been
- * registered with lxnetdev_notifier_register(). */
-void
-rtnetlink_notifier_unregister(struct rtnetlink_notifier *notifier)
-{
- list_remove(¬ifier->node);
- if (list_is_empty(&all_notifiers)) {
- nl_sock_destroy(notify_sock);
- notify_sock = NULL;
- }
-}
-
-/* Calls all of the registered notifiers, passing along any as-yet-unreported
- * netdev change events. */
-void
-rtnetlink_notifier_run(void)
-{
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
- if (!notify_sock) {
- return;
- }
-
- for (;;) {
- struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
- struct ofpbuf *buf;
- int error;
-
- error = nl_sock_recv(notify_sock, &buf, false);
- if (!error) {
- if (nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
- rtnlgrp_link_policy,
- attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
- struct ifinfomsg *ifinfo;
-
- ifinfo = (void *) ((char *) buf->data + NLMSG_HDRLEN);
- rtnetlink_report_change(buf->data, ifinfo, attrs);
- } else {
- VLOG_WARN_RL(&rl, "received bad rtnl message");
- rtnetlink_report_notify_error();
- }
- ofpbuf_delete(buf);
- } else if (error == EAGAIN) {
- return;
- } else {
- if (error == ENOBUFS) {
- VLOG_WARN_RL(&rl, "rtnetlink receive buffer overflowed");
- } else {
- VLOG_WARN_RL(&rl, "error reading rtnetlink socket: %s",
- strerror(error));
- }
- rtnetlink_report_notify_error();
- }
- }
-}
-
-/* Causes poll_block() to wake up when network device change notifications are
- * ready. */
-void
-rtnetlink_notifier_wait(void)
-{
- if (notify_sock) {
- nl_sock_wait(notify_sock, POLLIN);
- }
-}
-
-static void
-rtnetlink_report_change(const struct nlmsghdr *nlmsg,
- const struct ifinfomsg *ifinfo,
- struct nlattr *attrs[])
-{
- struct rtnetlink_notifier *notifier;
- struct rtnetlink_change change;
-
- COVERAGE_INC(rtnetlink_changed);
-
- change.nlmsg_type = nlmsg->nlmsg_type;
- change.ifi_index = ifinfo->ifi_index;
- change.ifname = nl_attr_get_string(attrs[IFLA_IFNAME]);
- change.master_ifindex = (attrs[IFLA_MASTER]
- ? nl_attr_get_u32(attrs[IFLA_MASTER]) : 0);
-
- LIST_FOR_EACH (notifier, struct rtnetlink_notifier, node,
- &all_notifiers) {
- notifier->cb(&change, notifier->aux);
- }
-}
-
-static void
-rtnetlink_report_notify_error(void)
+static int
+netdev_linux_get_ipv4(const struct netdev *netdev, struct in_addr *ip,
+ int cmd, const char *cmd_name)
{
- struct rtnetlink_notifier *notifier;
+ struct ifreq ifr;
+ int error;
- LIST_FOR_EACH (notifier, struct rtnetlink_notifier, node,
- &all_notifiers) {
- notifier->cb(NULL, notifier->aux);
+ 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;
}