| /* 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(" = %ld [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; |
| } |