Date: Fri Oct 9 21:32:10 2015 +0200
From: Petr Tesarik <ptesarik@suse.com>
Subject: Add Routable class
References: bsc#944201
Patch-mainline: v0.8.16
Git-commit: 8b5e2ba1639945f7c88e0ecddcfd2b895be68ce4

This class can be used to check whether a target is routable or not.
It uses a netlink socket to get routing information from the kernel
and can wait for changes in routing.

Signed-off-by: Petr Tesarik <ptesarik@suse.com>

---
 kdumptool/CMakeLists.txt |    2 
 kdumptool/routable.cc    |  410 +++++++++++++++++++++++++++++++++++++++++++++++
 kdumptool/routable.h     |   61 ++++++
 3 files changed, 473 insertions(+)

--- a/kdumptool/CMakeLists.txt
+++ b/kdumptool/CMakeLists.txt
@@ -90,6 +90,8 @@ SET(COMMON_SRC
     multipath.h
     calibrate.cc
     calibrate.h
+    routable.cc
+    routable.h
 )
 
 add_library(common STATIC ${COMMON_SRC})
--- /dev/null
+++ b/kdumptool/routable.cc
@@ -0,0 +1,410 @@
+/*
+ * (c) 2015, Petr Tesarik <ptesarik@suse.com>, SUSE LINUX GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include "global.h"
+#include "routable.h"
+#include "debug.h"
+
+//{{{ NetLink ------------------------------------------------------------------
+
+#define NETLINK_DEF_RECV_MAX	1024
+
+class NetLink {
+    public:
+	NetLink(unsigned subscribe = 0U,
+		size_t recv_max = NETLINK_DEF_RECV_MAX);
+
+	~NetLink();
+
+	int checkRoute(const struct addrinfo *ai);
+
+	int waitRouteChange(void);
+
+    protected:
+	class RecvCheck {
+	    public:
+		virtual bool check(const struct sockaddr_nl *nladdr,
+				   const struct nlmsghdr *nh) const = 0;
+	};
+	class RouteRecvCheck : public RecvCheck {
+	    public:
+		virtual bool check(const struct sockaddr_nl *nladdr,
+				   const struct nlmsghdr *nh) const;
+	};
+	class ReplyRecvCheck : public RecvCheck {
+	    public:
+		ReplyRecvCheck(unsigned peer, unsigned pid, unsigned seq)
+		    : m_peer(peer), m_pid(pid), m_seq(seq)
+		{}
+
+		virtual bool check(const struct sockaddr_nl *nladdr,
+				   const struct nlmsghdr *nh) const;
+	    private:
+		unsigned m_peer, m_pid, m_seq;
+	};
+
+	int receive(const RecvCheck &rc);
+
+	int talk(struct nlmsghdr *req, unsigned peer, unsigned groups);
+
+	struct nlmsghdr *message(void) const
+	throw ()
+	{ return m_message; }
+
+    private:
+	int m_fd;
+	struct sockaddr_nl m_local;
+	static unsigned m_seq;
+
+	size_t m_buflen;
+	unsigned char *m_buffer;
+	struct nlmsghdr *m_message;
+};
+
+unsigned NetLink::m_seq;
+
+// -----------------------------------------------------------------------------
+NetLink::NetLink(unsigned subscribe, size_t recv_max)
+    : m_buflen(recv_max)
+{
+    struct sockaddr_nl sa;
+    socklen_t salen;
+
+    m_buffer = new unsigned char[m_buflen];
+    m_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (m_fd < 0)
+	throw KSystemError("Cannot create netlink", errno);
+
+    memset(&sa, 0, sizeof(sa));
+    sa.nl_family = AF_NETLINK;
+    sa.nl_groups = subscribe;
+    if (bind(m_fd, (struct sockaddr *) &sa, sizeof sa) != 0)
+	throw KSystemError("Cannot bind netlink", errno);
+
+    salen = sizeof m_local;
+    if (getsockname(m_fd, (struct sockaddr*)&m_local, &salen) < 0)
+	throw KSystemError("Cannot get local netlink address", errno);
+    if (salen != sizeof m_local || m_local.nl_family != AF_NETLINK)
+	throw KError("Invalid local netlink address");
+}
+
+// -----------------------------------------------------------------------------
+NetLink::~NetLink()
+{
+    if (m_fd >= 0)
+	close(m_fd);
+
+    delete[] m_buffer;
+}
+
+// -----------------------------------------------------------------------------
+bool NetLink::RouteRecvCheck::check(const struct sockaddr_nl *nladdr,
+				    const struct nlmsghdr *nh) const
+{
+    Debug::debug()->trace("RouteRecvCheck::check(%u)",
+			  (unsigned)nh->nlmsg_type);
+
+    if (nh->nlmsg_type != RTM_NEWROUTE &&
+	nh->nlmsg_type != RTM_DELROUTE)
+	return false;
+
+    size_t rtlen = NLMSG_PAYLOAD(nh, 0);
+    if (rtlen < sizeof(struct rtmsg))
+	throw KError("Netlink rtmsg truncated");
+
+    struct rtmsg *rt = (struct rtmsg*)NLMSG_DATA(nh);
+    if (rt->rtm_flags & RTM_F_CLONED)
+	return false;
+
+    return true;
+}
+
+// -----------------------------------------------------------------------------
+bool NetLink::ReplyRecvCheck::check(const struct sockaddr_nl *nladdr,
+				    const struct nlmsghdr *nh) const
+{
+    return (nladdr->nl_pid == m_peer &&
+	    nh->nlmsg_pid == m_pid &&
+	    nh->nlmsg_seq == m_seq);
+}
+
+// -----------------------------------------------------------------------------
+int NetLink::receive(const RecvCheck &rc)
+{
+    struct sockaddr_nl nladdr;
+    struct iovec iov;
+    struct msghdr msg;
+
+    m_message = NULL;
+
+    msg.msg_name = &nladdr;
+    msg.msg_namelen = sizeof nladdr;
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    while (1) {
+	struct nlmsghdr *nh;
+	ssize_t len;
+
+	iov.iov_base = m_buffer;
+	iov.iov_len = m_buflen;
+	len = recvmsg(m_fd, &msg, 0);
+	if (len < 0)
+	    throw KSystemError("Cannot receive netlink message", errno);
+	if (!len)
+	    throw KError("EOF on netlink receive");
+	if (msg.msg_namelen != sizeof nladdr)
+	    throw KError("Invalid netlink sender address length");
+
+	for (nh = (struct nlmsghdr*)m_buffer; NLMSG_OK(nh, len);
+	     nh = NLMSG_NEXT(nh, len)) {
+	    if (rc.check(&nladdr, nh)) {
+		m_message = nh;
+
+		if (nh->nlmsg_type != NLMSG_ERROR)
+		    return 0;
+
+		size_t datalen = NLMSG_PAYLOAD(nh, 0);
+		if (datalen < sizeof(struct nlmsgerr))
+		    throw KError("Netlink ERROR reply truncated");
+
+		struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(nh);
+		return err->error;
+	    }
+	}
+
+	if (msg.msg_flags & MSG_TRUNC)
+	    throw KError("Netlink message truncated");
+
+	if (len)
+	    throw KError("Invalid netlink message length");
+    }
+}
+
+// -----------------------------------------------------------------------------
+int NetLink::waitRouteChange(void)
+{
+    Debug::debug()->trace("waitRouteChange()");
+
+    RouteRecvCheck rc;
+    return receive(rc);
+}
+
+// -----------------------------------------------------------------------------
+int NetLink::talk(struct nlmsghdr *req, unsigned peer, unsigned groups)
+{
+    struct sockaddr_nl nladdr;
+    struct iovec iov;
+    struct msghdr msg;
+    unsigned seq;
+    ssize_t len;
+
+    memset(&nladdr, 0, sizeof nladdr);
+    nladdr.nl_family = AF_NETLINK;
+    nladdr.nl_pid = peer;
+    nladdr.nl_groups = groups;
+
+    memset(&iov, 0, sizeof iov);
+    iov.iov_base = (void*)req;
+    iov.iov_len = req->nlmsg_len;
+
+    memset(&msg, 0, sizeof msg);
+    msg.msg_name = &nladdr;
+    msg.msg_namelen = sizeof nladdr;
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    req->nlmsg_seq = seq = ++m_seq;
+
+    len = sendmsg(m_fd, &msg, 0);
+    if (len < 0)
+	throw KSystemError("Cannot send netlink message", errno);
+
+    ReplyRecvCheck rc(peer, m_local.nl_pid, seq);
+    return receive(rc);
+}
+
+// -----------------------------------------------------------------------------
+int NetLink::checkRoute(const struct addrinfo *ai)
+{
+    size_t addrlen;
+    struct {
+	struct nlmsghdr  nh;
+	struct rtmsg     rt;
+	struct rtattr    rta;
+	union {
+	    struct in_addr in_addr;
+	    struct in6_addr in6_addr;
+	} u;
+    } req;
+    int res;
+
+    if (Debug::debug()->isDebugEnabled()) {
+	char ip[INET6_ADDRSTRLEN];
+	getnameinfo(ai->ai_addr, ai->ai_addrlen, ip, sizeof ip,
+		    NULL, 0, NI_NUMERICHOST);
+	Debug::debug()->trace("checkRoute(%s)", ip);
+    }
+
+    memset(&req, 0, sizeof req);
+
+    if (ai->ai_family == AF_INET) {
+	const struct sockaddr_in *sin =
+	    (struct sockaddr_in *)ai->ai_addr;
+
+	req.u.in_addr = sin->sin_addr;
+	addrlen = sizeof(struct in_addr);
+    } else {
+	const struct sockaddr_in6 *sin6 =
+	    (struct sockaddr_in6 *)ai->ai_addr;
+
+	req.u.in6_addr = sin6->sin6_addr;
+	addrlen = sizeof(struct in6_addr);
+    }
+
+    req.rta.rta_type = RTA_DST;
+    req.rta.rta_len = RTA_LENGTH(addrlen);
+    req.rt.rtm_family = ai->ai_family;
+    req.rt.rtm_dst_len = addrlen * 8;
+    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg) + req.rta.rta_len);
+    req.nh.nlmsg_type = RTM_GETROUTE;
+    req.nh.nlmsg_flags = NLM_F_REQUEST;
+
+    if ( (res = talk(&req.nh, 0, 0)) < 0)
+	return res;
+
+    struct nlmsghdr *nh = message();
+    if (nh->nlmsg_type != RTM_NEWROUTE)
+	throw KError("Netlink RTM_GETROUTE reply mismatch");
+
+    size_t rtlen = NLMSG_PAYLOAD(nh, 0);
+    if (rtlen < sizeof(struct rtmsg))
+	throw KError("Netlink rtmsg truncated");
+
+    struct rtmsg *rt = (struct rtmsg*)NLMSG_DATA(nh);
+    switch (rt->rtm_type) {
+    case RTN_UNSPEC:
+    case RTN_UNICAST:
+    case RTN_LOCAL:
+    case RTN_BROADCAST:
+    case RTN_ANYCAST:
+    case RTN_MULTICAST:
+	return 0;
+    case RTN_UNREACHABLE:
+	return -EHOSTUNREACH;
+    case RTN_BLACKHOLE:
+	return -EINVAL;
+    case RTN_PROHIBIT:
+	return -EACCES;
+    case RTN_THROW:
+	return -EAGAIN;
+    default:
+	return -EINVAL;
+    }
+}
+
+//}}}
+
+//{{{ Routable -----------------------------------------------------------------
+
+// -----------------------------------------------------------------------------
+Routable::~Routable()
+{
+    if (m_ai)
+	freeaddrinfo(m_ai);
+}
+
+// -----------------------------------------------------------------------------
+bool Routable::hasRoute(void)
+{
+    NetLink nl;
+    struct addrinfo *p;
+
+    Debug::debug()->trace("hasRoute(%s)", m_host.c_str());
+
+    for (p = m_ai; p; p = p->ai_next) {
+	if (nl.checkRoute(p) == 0)
+	    return true;
+    }
+
+    return false;
+}
+
+// -----------------------------------------------------------------------------
+bool Routable::resolve(void)
+    throw (KError)
+{
+    struct addrinfo hints;
+    int res;
+
+    Debug::debug()->trace("resolve(%s)", m_host.c_str());
+
+    if (m_ai)
+	freeaddrinfo(m_ai);
+
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_RAW;
+    do {
+	res = getaddrinfo(m_host.c_str(), NULL, &hints, &m_ai);
+    } while (res == EAI_AGAIN);
+
+    if (res == 0)
+	return true;
+
+    if (res == EAI_SYSTEM)
+	throw KSystemError("Name resolution failed", errno);
+
+    if (res != EAI_NONAME && res != EAI_FAIL && res != EAI_NODATA)
+	throw KGaiError("Name resolution failed", res);
+
+    return false;
+}
+
+// -----------------------------------------------------------------------------
+bool Routable::check(void)
+{
+    NetLink nl(RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE);
+
+    while (!resolve())
+	if (nl.waitRouteChange() != 0)
+	    return false;
+
+    while (!hasRoute())
+	if (nl.waitRouteChange() != 0)
+	    return false;
+
+    return true;
+}
+
+//}}}
+
+// vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
--- /dev/null
+++ b/kdumptool/routable.h
@@ -0,0 +1,61 @@
+/*
+ * (c) 2015, Petr Tesarik <ptesarik@suse.com>, SUSE LINUX GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef ROUTABLE_H
+#define ROUTABLE_H
+
+#include <string>
+
+#include "subcommand.h"
+#include "global.h"
+
+//{{{ Routable -----------------------------------------------------------------
+
+struct addrinfo;
+
+/**
+ * Remote target that can be checked for routability.
+ */
+class Routable {
+
+    public:
+	Routable(const std::string &host)
+		: m_host(host), m_ai(NULL)
+	{}
+
+	~Routable();
+
+	bool check(void);
+
+    protected:
+	bool resolve(void)
+	throw (KError);
+
+	bool hasRoute(void);
+
+    private:
+	int m_nlfd;
+	std::string m_host;
+	struct addrinfo *m_ai;
+};
+
+//}}}
+
+#endif /* ROUTABLE_H */
+
+// vim: set sw=4 ts=4 fdm=marker et: :collapseFolds=1:
