/* 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 #include #include #include #include #include #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; }