aboutsummaryrefslogblamecommitdiffstats
path: root/fs/afs/use-rtnetlink.c
blob: f8991c700e02be4764055357edcabef8c62b5fce (plain) (tree)




















































































































































































































































                                                                              
                                                               


































































































































































































































                                                                                  
/* RTNETLINK client
 *
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * 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.
 */
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_addr.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <net/netlink.h>
#include "internal.h"

struct afs_rtm_desc {
	struct socket		*nlsock;
	struct afs_interface	*bufs;
	u8			*mac;
	size_t			nbufs;
	size_t			maxbufs;
	void			*data;
	ssize_t			datalen;
	size_t			datamax;
	int			msg_seq;
	unsigned		mac_index;
	bool			wantloopback;
	int (*parse)(struct afs_rtm_desc *, struct nlmsghdr *);
};

/*
 * parse an RTM_GETADDR response
 */
static int afs_rtm_getaddr_parse(struct afs_rtm_desc *desc,
				 struct nlmsghdr *nlhdr)
{
	struct afs_interface *this;
	struct ifaddrmsg *ifa;
	struct rtattr *rtattr;
	const char *name;
	size_t len;

	ifa = (struct ifaddrmsg *) NLMSG_DATA(nlhdr);

	_enter("{ix=%d,af=%d}", ifa->ifa_index, ifa->ifa_family);

	if (ifa->ifa_family != AF_INET) {
		_leave(" = 0 [family %d]", ifa->ifa_family);
		return 0;
	}
	if (desc->nbufs >= desc->maxbufs) {
		_leave(" = 0 [max %zu/%zu]", desc->nbufs, desc->maxbufs);
		return 0;
	}

	this = &desc->bufs[desc->nbufs];

	this->index = ifa->ifa_index;
	this->netmask.s_addr = inet_make_mask(ifa->ifa_prefixlen);
	this->mtu = 0;

	rtattr = NLMSG_DATA(nlhdr) + NLMSG_ALIGN(sizeof(struct ifaddrmsg));
	len = NLMSG_PAYLOAD(nlhdr, sizeof(struct ifaddrmsg));

	name = "unknown";
	for (; RTA_OK(rtattr, len); rtattr = RTA_NEXT(rtattr, len)) {
		switch (rtattr->rta_type) {
		case IFA_ADDRESS:
			memcpy(&this->address, RTA_DATA(rtattr), 4);
			break;
		case IFA_LABEL:
			name = RTA_DATA(rtattr);
			break;
		}
	}

	_debug("%s: "NIPQUAD_FMT"/"NIPQUAD_FMT,
	       name, NIPQUAD(this->address), NIPQUAD(this->netmask));

	desc->nbufs++;
	_leave(" = 0");
	return 0;
}

/*
 * parse an RTM_GETLINK response for MTUs
 */
static int afs_rtm_getlink_if_parse(struct afs_rtm_desc *desc,
				    struct nlmsghdr *nlhdr)
{
	struct afs_interface *this;
	struct ifinfomsg *ifi;
	struct rtattr *rtattr;
	const char *name;
	size_t len, loop;

	ifi = (struct ifinfomsg *) NLMSG_DATA(nlhdr);

	_enter("{ix=%d}", ifi->ifi_index);

	for (loop = 0; loop < desc->nbufs; loop++) {
		this = &desc->bufs[loop];
		if (this->index == ifi->ifi_index)
			goto found;
	}

	_leave(" = 0 [no match]");
	return 0;

found:
	if (ifi->ifi_type == ARPHRD_LOOPBACK && !desc->wantloopback) {
		_leave(" = 0 [loopback]");
		return 0;
	}

	rtattr = NLMSG_DATA(nlhdr) + NLMSG_ALIGN(sizeof(struct ifinfomsg));
	len = NLMSG_PAYLOAD(nlhdr, sizeof(struct ifinfomsg));

	name = "unknown";
	for (; RTA_OK(rtattr, len); rtattr = RTA_NEXT(rtattr, len)) {
		switch (rtattr->rta_type) {
		case IFLA_MTU:
			memcpy(&this->mtu, RTA_DATA(rtattr), 4);
			break;
		case IFLA_IFNAME:
			name = RTA_DATA(rtattr);
			break;
		}
	}

	_debug("%s: "NIPQUAD_FMT"/"NIPQUAD_FMT" mtu %u",
	       name, NIPQUAD(this->address), NIPQUAD(this->netmask),
	       this->mtu);

	_leave(" = 0");
	return 0;
}

/*
 * parse an RTM_GETLINK response for the MAC address belonging to the lowest
 * non-internal interface
 */
static int afs_rtm_getlink_mac_parse(struct afs_rtm_desc *desc,
				     struct nlmsghdr *nlhdr)
{
	struct ifinfomsg *ifi;
	struct rtattr *rtattr;
	const char *name;
	size_t remain, len;
	bool set;

	ifi = (struct ifinfomsg *) NLMSG_DATA(nlhdr);

	_enter("{ix=%d}", ifi->ifi_index);

	if (ifi->ifi_index >= desc->mac_index) {
		_leave(" = 0 [high]");
		return 0;
	}
	if (ifi->ifi_type == ARPHRD_LOOPBACK) {
		_leave(" = 0 [loopback]");
		return 0;
	}

	rtattr = NLMSG_DATA(nlhdr) + NLMSG_ALIGN(sizeof(struct ifinfomsg));
	remain = NLMSG_PAYLOAD(nlhdr, sizeof(struct ifinfomsg));

	name = "unknown";
	set = false;
	for (; RTA_OK(rtattr, remain); rtattr = RTA_NEXT(rtattr, remain)) {
		switch (rtattr->rta_type) {
		case IFLA_ADDRESS:
			len = RTA_PAYLOAD(rtattr);
			memcpy(desc->mac, RTA_DATA(rtattr),
			       min_t(size_t, len, 6));
			desc->mac_index = ifi->ifi_index;
			set = true;
			break;
		case IFLA_IFNAME:
			name = RTA_DATA(rtattr);
			break;
		}
	}

	if (set)
		_debug("%s: %02x:%02x:%02x:%02x:%02x:%02x",
		       name,
		       desc->mac[0], desc->mac[1], desc->mac[2],
		       desc->mac[3], desc->mac[4], desc->mac[5]);

	_leave(" = 0");
	return 0;
}

/*
 * read the rtnetlink response and pass to parsing routine
 */
static int afs_read_rtm(struct afs_rtm_desc *desc)
{
	struct nlmsghdr *nlhdr, tmphdr;
	struct msghdr msg;
	struct kvec iov[1];
	void *data;
	bool last = false;
	int len, ret, remain;

	_enter("");

	do {
		/* first of all peek to see how big the packet is */
		memset(&msg, 0, sizeof(msg));
		iov[0].iov_base = &tmphdr;
		iov[0].iov_len = sizeof(tmphdr);
		len = kernel_recvmsg(desc->nlsock, &msg, iov, 1,
				     sizeof(tmphdr), MSG_PEEK | MSG_TRUNC);
		if (len < 0) {
			_leave(" = %d [peek]", len);
			return len;
		}
		if (len == 0)
			continue;
		if (len < sizeof(tmphdr) || len < NLMSG_PAYLOAD(&tmphdr, 0)) {
			_leave(" = -EMSGSIZE");
			return -EMSGSIZE;
		}

		if (desc->datamax < len) {
			kfree(desc->data);
			desc->data = NULL;
			data = kmalloc(len, GFP_KERNEL);
			if (!data)
				return -ENOMEM;
			desc->data = data;
		}
		desc->datamax = len;

		/* read all the data from this packet */
		iov[0].iov_base = desc->data;
		iov[0].iov_len = desc->datamax;
		desc->datalen = kernel_recvmsg(desc->nlsock, &msg, iov, 1,
					       desc->datamax, 0);
		if (desc->datalen < 0) {
			_leave(" = %zd [recv]", desc->datalen);
			return desc->datalen;
		}

		nlhdr = desc->data;

		/* check if the header is valid */
		if (!NLMSG_OK(nlhdr, desc->datalen) ||
		    nlhdr->nlmsg_type == NLMSG_ERROR) {
			_leave(" = -EIO");
			return -EIO;
		}

		/* see if this is the last message */
		if (nlhdr->nlmsg_type == NLMSG_DONE ||
		    !(nlhdr->nlmsg_flags & NLM_F_MULTI))
			last = true;

		/* parse the bits we got this time */
		nlmsg_for_each_msg(nlhdr, desc->data, desc->datalen, remain) {
			ret = desc->parse(desc, nlhdr);
			if (ret < 0) {
				_leave(" = %d [parse]", ret);
				return ret;
			}
		}

	} while (!last);

	_leave(" = 0");
	return 0;
}

/*
 * list the interface bound addresses to get the address and netmask
 */
static int afs_rtm_getaddr(struct afs_rtm_desc *desc)
{
	struct msghdr msg;
	struct kvec iov[1];
	int ret;

	struct {
		struct nlmsghdr nl_msg __attribute__((aligned(NLMSG_ALIGNTO)));
		struct ifaddrmsg addr_msg __attribute__((aligned(NLMSG_ALIGNTO)));
	} request;

	_enter("");

	memset(&request, 0, sizeof(request));

	request.nl_msg.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
	request.nl_msg.nlmsg_type = RTM_GETADDR;
	request.nl_msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
	request.nl_msg.nlmsg_seq = desc->msg_seq++;
	request.nl_msg.nlmsg_pid = 0;

	memset(&msg, 0, sizeof(msg));
	iov[0].iov_base = &request;
	iov[0].iov_len = sizeof(request);

	ret = kernel_sendmsg(desc->nlsock, &msg, iov, 1, iov[0].iov_len);
	_leave(" = %d", ret);
	return ret;
}

/*
 * list the interface link statuses to get the MTUs
 */
static int afs_rtm_getlink(struct afs_rtm_desc *desc)
{
	struct msghdr msg;
	struct kvec iov[1];
	int ret;

	struct {
		struct nlmsghdr nl_msg __attribute__((aligned(NLMSG_ALIGNTO)));
		struct ifinfomsg link_msg __attribute__((aligned(NLMSG_ALIGNTO)));
	} request;

	_enter("");

	memset(&request, 0, sizeof(request));

	request.nl_msg.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	request.nl_msg.nlmsg_type = RTM_GETLINK;
	request.nl_msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
	request.nl_msg.nlmsg_seq = desc->msg_seq++;
	request.nl_msg.nlmsg_pid = 0;

	memset(&msg, 0, sizeof(msg));
	iov[0].iov_base = &request;
	iov[0].iov_len = sizeof(request);

	ret = kernel_sendmsg(desc->nlsock, &msg, iov, 1, iov[0].iov_len);
	_leave(" = %d", ret);
	return ret;
}

/*
 * cull any interface records for which there isn't an MTU value
 */
static void afs_cull_interfaces(struct afs_rtm_desc *desc)
{
	struct afs_interface *bufs = desc->bufs;
	size_t nbufs = desc->nbufs;
	int loop, point = 0;

	_enter("{%zu}", nbufs);

	for (loop = 0; loop < nbufs; loop++) {
		if (desc->bufs[loop].mtu != 0) {
			if (loop != point) {
				ASSERTCMP(loop, >, point);
				bufs[point] = bufs[loop];
			}
			point++;
		}
	}

	desc->nbufs = point;
	_leave(" [%zu/%zu]", desc->nbufs, nbufs);
}

/*
 * get a list of this system's interface IPv4 addresses, netmasks and MTUs
 * - returns the number of interface records in the buffer
 */
int afs_get_ipv4_interfaces(struct afs_interface *bufs, size_t maxbufs,
			    bool wantloopback)
{
	struct afs_rtm_desc desc;
	int ret, loop;

	_enter("");

	memset(&desc, 0, sizeof(desc));
	desc.bufs = bufs;
	desc.maxbufs = maxbufs;
	desc.wantloopback = wantloopback;

	ret = sock_create_kern(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE,
			       &desc.nlsock);
	if (ret < 0) {
		_leave(" = %d [sock]", ret);
		return ret;
	}

	/* issue RTM_GETADDR */
	desc.parse = afs_rtm_getaddr_parse;
	ret = afs_rtm_getaddr(&desc);
	if (ret < 0)
		goto error;
	ret = afs_read_rtm(&desc);
	if (ret < 0)
		goto error;

	/* issue RTM_GETLINK */
	desc.parse = afs_rtm_getlink_if_parse;
	ret = afs_rtm_getlink(&desc);
	if (ret < 0)
		goto error;
	ret = afs_read_rtm(&desc);
	if (ret < 0)
		goto error;

	afs_cull_interfaces(&desc);
	ret = desc.nbufs;

	for (loop = 0; loop < ret; loop++)
		_debug("[%d] "NIPQUAD_FMT"/"NIPQUAD_FMT" mtu %u",
		       bufs[loop].index,
		       NIPQUAD(bufs[loop].address),
		       NIPQUAD(bufs[loop].netmask),
		       bufs[loop].mtu);

error:
	kfree(desc.data);
	sock_release(desc.nlsock);
	_leave(" = %d", ret);
	return ret;
}

/*
 * get a MAC address from a random ethernet interface that has a real one
 * - the buffer should be 6 bytes in size
 */
int afs_get_MAC_address(u8 mac[6])
{
	struct afs_rtm_desc desc;
	int ret;

	_enter("");

	memset(&desc, 0, sizeof(desc));
	desc.mac = mac;
	desc.mac_index = UINT_MAX;

	ret = sock_create_kern(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE,
			       &desc.nlsock);
	if (ret < 0) {
		_leave(" = %d [sock]", ret);
		return ret;
	}

	/* issue RTM_GETLINK */
	desc.parse = afs_rtm_getlink_mac_parse;
	ret = afs_rtm_getlink(&desc);
	if (ret < 0)
		goto error;
	ret = afs_read_rtm(&desc);
	if (ret < 0)
		goto error;

	if (desc.mac_index < UINT_MAX) {
		/* got a MAC address */
		_debug("[%d] %02x:%02x:%02x:%02x:%02x:%02x",
		       desc.mac_index,
		       mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	} else {
		ret = -ENONET;
	}

error:
	sock_release(desc.nlsock);
	_leave(" = %d", ret);
	return ret;
}