struct sk_buff *, bool);
 static int afs_deliver_cb_probe(struct afs_call *, struct sk_buff *, bool);
 static int afs_deliver_cb_callback(struct afs_call *, struct sk_buff *, bool);
+static int afs_deliver_cb_get_capabilities(struct afs_call *, struct sk_buff *,
+                                          bool);
 static void afs_cm_destructor(struct afs_call *);
 
 /*
        .destructor     = afs_cm_destructor,
 };
 
+/*
+ * CB.GetCapabilities operation type
+ */
+static const struct afs_call_type afs_SRXCBGetCapabilites = {
+       .name           = "CB.GetCapabilities",
+       .deliver        = afs_deliver_cb_get_capabilities,
+       .abort_to_error = afs_abort_to_error,
+       .destructor     = afs_cm_destructor,
+};
+
 /*
  * route an incoming cache manager call
  * - return T if supported, F if not
        case CBProbe:
                call->type = &afs_SRXCBProbe;
                return true;
+       case CBGetCapabilities:
+               call->type = &afs_SRXCBGetCapabilites;
+               return true;
        default:
                return false;
        }
        schedule_work(&call->work);
        return 0;
 }
+
+/*
+ * allow the fileserver to ask about the cache manager's capabilities
+ */
+static void SRXAFSCB_GetCapabilities(struct work_struct *work)
+{
+       struct afs_interface *ifs;
+       struct afs_call *call = container_of(work, struct afs_call, work);
+       int loop, nifs;
+
+       struct {
+               struct /* InterfaceAddr */ {
+                       __be32 nifs;
+                       __be32 uuid[11];
+                       __be32 ifaddr[32];
+                       __be32 netmask[32];
+                       __be32 mtu[32];
+               } ia;
+               struct /* Capabilities */ {
+                       __be32 capcount;
+                       __be32 caps[1];
+               } cap;
+       } reply;
+
+       _enter("");
+
+       nifs = 0;
+       ifs = kcalloc(32, sizeof(*ifs), GFP_KERNEL);
+       if (ifs) {
+               nifs = afs_get_ipv4_interfaces(ifs, 32, false);
+               if (nifs < 0) {
+                       kfree(ifs);
+                       ifs = NULL;
+                       nifs = 0;
+               }
+       }
+
+       memset(&reply, 0, sizeof(reply));
+       reply.ia.nifs = htonl(nifs);
+
+       reply.ia.uuid[0] = htonl(afs_uuid.time_low);
+       reply.ia.uuid[1] = htonl(afs_uuid.time_mid);
+       reply.ia.uuid[2] = htonl(afs_uuid.time_hi_and_version);
+       reply.ia.uuid[3] = htonl((s8) afs_uuid.clock_seq_hi_and_reserved);
+       reply.ia.uuid[4] = htonl((s8) afs_uuid.clock_seq_low);
+       for (loop = 0; loop < 6; loop++)
+               reply.ia.uuid[loop + 5] = htonl((s8) afs_uuid.node[loop]);
+
+       if (ifs) {
+               for (loop = 0; loop < nifs; loop++) {
+                       reply.ia.ifaddr[loop] = ifs[loop].address.s_addr;
+                       reply.ia.netmask[loop] = ifs[loop].netmask.s_addr;
+                       reply.ia.mtu[loop] = htonl(ifs[loop].mtu);
+               }
+       }
+
+       reply.cap.capcount = htonl(1);
+       reply.cap.caps[0] = htonl(AFS_CAP_ERROR_TRANSLATION);
+       afs_send_simple_reply(call, &reply, sizeof(reply));
+
+       _leave("");
+}
+
+/*
+ * deliver request data to a CB.GetCapabilities call
+ */
+static int afs_deliver_cb_get_capabilities(struct afs_call *call,
+                                          struct sk_buff *skb, bool last)
+{
+       _enter(",{%u},%d", skb->len, last);
+
+       if (skb->len > 0)
+               return -EBADMSG;
+       if (!last)
+               return 0;
+
+       /* no unmarshalling required */
+       call->state = AFS_CALL_REPLYING;
+
+       INIT_WORK(&call->work, SRXAFSCB_GetCapabilities);
+       schedule_work(&call->work);
+       return 0;
+}
 
--- /dev/null
+/* 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;
+}