freebsd-skq/sys/contrib/rdma/rdma_addr.c
Robert Watson 8c0fec805f Modify most routines returning 'struct ifaddr *' to return references
rather than pointers, requiring callers to properly dispose of those
references.  The following routines now return references:

  ifaddr_byindex
  ifa_ifwithaddr
  ifa_ifwithbroadaddr
  ifa_ifwithdstaddr
  ifa_ifwithnet
  ifaof_ifpforaddr
  ifa_ifwithroute
  ifa_ifwithroute_fib
  rt_getifa
  rt_getifa_fib
  IFP_TO_IA
  ip_rtaddr
  in6_ifawithifp
  in6ifa_ifpforlinklocal
  in6ifa_ifpwithaddr
  in6_ifadd
  carp_iamatch6
  ip6_getdstifaddr

Remove unused macro which didn't have required referencing:

  IFP_TO_IA6

This closes many small races in which changes to interface
or address lists while an ifaddr was in use could lead to use of freed
memory (etc).  In a few cases, add missing if_addr_list locking
required to safely acquire references.

Because of a lack of deep copying support, we accept a race in which
an in6_ifaddr pointed to by mbuf tags and extracted with
ip6_getdstifaddr() doesn't hold a reference while in transmit.  Once
we have mbuf tag deep copy support, this can be fixed.

Reviewed by:	bz
Obtained from:	Apple, Inc. (portions)
MFC after:	6 weeks (portions)
2009-06-23 20:19:09 +00:00

414 lines
10 KiB
C

/*
* Copyright (c) 2005 Voltaire Inc. All rights reserved.
* Copyright (c) 2002-2005, Network Appliance, Inc. All rights reserved.
* Copyright (c) 1999-2005, Mellanox Technologies, Inc. All rights reserved.
* Copyright (c) 2005 Intel Corporation. All rights reserved.
*
* This Software is licensed under one of the following licenses:
*
* 1) under the terms of the "Common Public License 1.0" a copy of which is
* available from the Open Source Initiative, see
* http://www.opensource.org/licenses/cpl.php.
*
* 2) under the terms of the "The BSD License" a copy of which is
* available from the Open Source Initiative, see
* http://www.opensource.org/licenses/bsd-license.php.
*
* 3) under the terms of the "GNU General Public License (GPL) Version 2" a
* copy of which is available from the Open Source Initiative, see
* http://www.opensource.org/licenses/gpl-license.php.
*
* Licensee has the right to choose one of the above licenses.
*
* Redistributions of source code must retain the above copyright
* notice and one of the license notices.
*
* Redistributions in binary form must reproduce both the above copyright
* notice, one of the license notices in the documentation
* and/or other materials provided with the distribution.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/condvar.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/mutex.h>
#include <sys/rwlock.h>
#include <sys/queue.h>
#include <sys/taskqueue.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/route.h>
#include <net80211/ieee80211_freebsd.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <contrib/rdma/ib_addr.h>
struct addr_req {
TAILQ_ENTRY(addr_req) entry;
struct sockaddr src_addr;
struct sockaddr dst_addr;
struct rdma_dev_addr *addr;
struct rdma_addr_client *client;
void *context;
void (*callback)(int status, struct sockaddr *src_addr,
struct rdma_dev_addr *addr, void *context);
unsigned long timeout;
int status;
};
static void process_req(void *ctx, int pending);
static struct mtx lock;
static TAILQ_HEAD(addr_req_list, addr_req) req_list;
static struct task addr_task;
static struct taskqueue *addr_taskq;
static struct callout addr_ch;
static eventhandler_tag route_event_tag;
static void addr_timeout(void *arg)
{
taskqueue_enqueue(addr_taskq, &addr_task);
}
void rdma_addr_register_client(struct rdma_addr_client *client)
{
mtx_init(&client->lock, "rdma_addr client lock", NULL, MTX_DUPOK|MTX_DEF);
cv_init(&client->comp, "rdma_addr cv");
client->refcount = 1;
}
static inline void put_client(struct rdma_addr_client *client)
{
mtx_lock(&client->lock);
if (--client->refcount == 0) {
cv_broadcast(&client->comp);
}
mtx_unlock(&client->lock);
}
void rdma_addr_unregister_client(struct rdma_addr_client *client)
{
put_client(client);
mtx_lock(&client->lock);
if (client->refcount) {
cv_wait(&client->comp, &client->lock);
}
mtx_unlock(&client->lock);
}
int rdma_copy_addr(struct rdma_dev_addr *dev_addr, struct ifnet *dev,
const unsigned char *dst_dev_addr)
{
dev_addr->dev_type = RDMA_NODE_RNIC;
memcpy(dev_addr->src_dev_addr, IF_LLADDR(dev), MAX_ADDR_LEN);
memcpy(dev_addr->broadcast, dev->if_broadcastaddr, MAX_ADDR_LEN);
if (dst_dev_addr)
memcpy(dev_addr->dst_dev_addr, dst_dev_addr, MAX_ADDR_LEN);
return 0;
}
int rdma_translate_ip(struct sockaddr *addr, struct rdma_dev_addr *dev_addr)
{
struct ifaddr *ifa;
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
uint16_t port = sin->sin_port;
int ret;
sin->sin_port = 0;
ifa = ifa_ifwithaddr(addr);
sin->sin_port = port;
if (!ifa)
return (EADDRNOTAVAIL);
ret = rdma_copy_addr(dev_addr, ifa->ifa_ifp, NULL);
ifa_free(ifa);
return (ret);
}
static void queue_req(struct addr_req *req)
{
struct addr_req *tmp_req = NULL;
mtx_lock(&lock);
TAILQ_FOREACH_REVERSE(tmp_req, &req_list, addr_req_list, entry)
if (time_after_eq(req->timeout, tmp_req->timeout))
break;
if (tmp_req)
TAILQ_INSERT_AFTER(&req_list, tmp_req, req, entry);
else
TAILQ_INSERT_TAIL(&req_list, req, entry);
if (TAILQ_FIRST(&req_list) == req)
callout_reset(&addr_ch, req->timeout - ticks, addr_timeout, NULL);
mtx_unlock(&lock);
}
#ifdef needed
static void addr_send_arp(struct sockaddr_in *dst_in)
{
struct route iproute;
struct sockaddr_in *dst = (struct sockaddr_in *)&iproute.ro_dst;
char dmac[ETHER_ADDR_LEN];
struct llentry *lle;
bzero(&iproute, sizeof iproute);
*dst = *dst_in;
rtalloc(&iproute);
if (iproute.ro_rt == NULL);
return;
arpresolve(iproute.ro_rt->rt_ifp, iproute.ro_rt, NULL,
rt_key(iproute.ro_rt), dmac, &lle);
RTFREE(iproute.ro_rt);
}
#endif
static int addr_resolve_remote(struct sockaddr_in *src_in,
struct sockaddr_in *dst_in,
struct rdma_dev_addr *addr)
{
int ret = 0;
struct route iproute;
struct sockaddr_in *dst = (struct sockaddr_in *)&iproute.ro_dst;
char dmac[ETHER_ADDR_LEN];
struct llentry *lle;
bzero(&iproute, sizeof iproute);
*dst = *dst_in;
rtalloc(&iproute);
if (iproute.ro_rt == NULL) {
ret = EHOSTUNREACH;
goto out;
}
/* If the device does ARP internally, return 'done' */
if (iproute.ro_rt->rt_ifp->if_flags & IFF_NOARP) {
rdma_copy_addr(addr, iproute.ro_rt->rt_ifp, NULL);
goto put;
}
ret = arpresolve(iproute.ro_rt->rt_ifp, iproute.ro_rt, NULL,
rt_key(iproute.ro_rt), dmac, &lle);
if (ret) {
goto put;
}
if (!src_in->sin_addr.s_addr) {
src_in->sin_len = sizeof *src_in;
src_in->sin_family = dst_in->sin_family;
src_in->sin_addr.s_addr = ((struct sockaddr_in *)iproute.ro_rt->rt_ifa->ifa_addr)->sin_addr.s_addr;
}
ret = rdma_copy_addr(addr, iproute.ro_rt->rt_ifp, dmac);
put:
RTFREE(iproute.ro_rt);
out:
return ret;
}
static void process_req(void *ctx, int pending)
{
struct addr_req *req, *tmp_req;
struct sockaddr_in *src_in, *dst_in;
TAILQ_HEAD(, addr_req) done_list;
TAILQ_INIT(&done_list);
mtx_lock(&lock);
TAILQ_FOREACH_SAFE(req, &req_list, entry, tmp_req) {
if (req->status == EWOULDBLOCK) {
src_in = (struct sockaddr_in *) &req->src_addr;
dst_in = (struct sockaddr_in *) &req->dst_addr;
req->status = addr_resolve_remote(src_in, dst_in,
req->addr);
if (req->status && time_after_eq(ticks, req->timeout))
req->status = ETIMEDOUT;
else if (req->status == EWOULDBLOCK)
continue;
}
TAILQ_REMOVE(&req_list, req, entry);
TAILQ_INSERT_TAIL(&done_list, req, entry);
}
if (!TAILQ_EMPTY(&req_list)) {
req = TAILQ_FIRST(&req_list);
callout_reset(&addr_ch, req->timeout - ticks, addr_timeout,
NULL);
}
mtx_unlock(&lock);
TAILQ_FOREACH_SAFE(req, &done_list, entry, tmp_req) {
TAILQ_REMOVE(&done_list, req, entry);
req->callback(req->status, &req->src_addr, req->addr,
req->context);
put_client(req->client);
free(req, M_DEVBUF);
}
}
int rdma_resolve_ip(struct rdma_addr_client *client,
struct sockaddr *src_addr, struct sockaddr *dst_addr,
struct rdma_dev_addr *addr, int timeout_ms,
void (*callback)(int status, struct sockaddr *src_addr,
struct rdma_dev_addr *addr, void *context),
void *context)
{
struct sockaddr_in *src_in, *dst_in;
struct addr_req *req;
int ret = 0;
req = malloc(sizeof *req, M_DEVBUF, M_NOWAIT);
if (!req)
return (ENOMEM);
memset(req, 0, sizeof *req);
if (src_addr)
memcpy(&req->src_addr, src_addr, ip_addr_size(src_addr));
memcpy(&req->dst_addr, dst_addr, ip_addr_size(dst_addr));
req->addr = addr;
req->callback = callback;
req->context = context;
req->client = client;
mtx_lock(&client->lock);
client->refcount++;
mtx_unlock(&client->lock);
src_in = (struct sockaddr_in *) &req->src_addr;
dst_in = (struct sockaddr_in *) &req->dst_addr;
req->status = addr_resolve_remote(src_in, dst_in, addr);
switch (req->status) {
case 0:
req->timeout = ticks;
queue_req(req);
break;
case EWOULDBLOCK:
req->timeout = msecs_to_ticks(timeout_ms) + ticks;
queue_req(req);
#ifdef needed
addr_send_arp(dst_in);
#endif
break;
default:
ret = req->status;
mtx_lock(&client->lock);
client->refcount--;
mtx_unlock(&client->lock);
free(req, M_DEVBUF);
break;
}
return ret;
}
void rdma_addr_cancel(struct rdma_dev_addr *addr)
{
struct addr_req *req, *tmp_req;
mtx_lock(&lock);
TAILQ_FOREACH_SAFE(req, &req_list, entry, tmp_req) {
if (req->addr == addr) {
req->status = ECANCELED;
req->timeout = ticks;
TAILQ_REMOVE(&req_list, req, entry);
TAILQ_INSERT_HEAD(&req_list, req, entry);
callout_reset(&addr_ch, req->timeout - ticks, addr_timeout, NULL);
break;
}
}
mtx_unlock(&lock);
}
static void
route_event_arp_update(void *unused, struct rtentry *rt0, uint8_t *enaddr,
struct sockaddr *sa)
{
callout_stop(&addr_ch);
taskqueue_enqueue(addr_taskq, &addr_task);
}
static int addr_init(void)
{
TAILQ_INIT(&req_list);
mtx_init(&lock, "rdma_addr req_list lock", NULL, MTX_DEF);
addr_taskq = taskqueue_create("rdma_addr_taskq", M_NOWAIT,
taskqueue_thread_enqueue, &addr_taskq);
if (addr_taskq == NULL) {
printf("failed to allocate rdma_addr taskqueue\n");
return (ENOMEM);
}
taskqueue_start_threads(&addr_taskq, 1, PI_NET, "rdma_addr taskq");
TASK_INIT(&addr_task, 0, process_req, NULL);
callout_init(&addr_ch, TRUE);
route_event_tag = EVENTHANDLER_REGISTER(route_arp_update_event,
route_event_arp_update, NULL, EVENTHANDLER_PRI_ANY);
return 0;
}
static void addr_cleanup(void)
{
EVENTHANDLER_DEREGISTER(route_event_arp_update, route_event_tag);
callout_stop(&addr_ch);
taskqueue_drain(addr_taskq, &addr_task);
taskqueue_free(addr_taskq);
}
static int
addr_load(module_t mod, int cmd, void *arg)
{
int err = 0;
switch (cmd) {
case MOD_LOAD:
printf("Loading rdma_addr.\n");
addr_init();
break;
case MOD_QUIESCE:
break;
case MOD_UNLOAD:
printf("Unloading rdma_addr.\n");
addr_cleanup();
break;
case MOD_SHUTDOWN:
break;
default:
err = EOPNOTSUPP;
break;
}
return (err);
}
static moduledata_t mod_data = {
"rdma_addr",
addr_load,
0
};
MODULE_VERSION(rdma_addr, 1);
DECLARE_MODULE(rdma_addr, mod_data, SI_SUB_EXEC, SI_ORDER_ANY);