2008-05-05 18:35:55 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
2012-06-19 07:34:13 +00:00
|
|
|
memset(dev_addr->src_dev_addr, 0, MAX_ADDR_LEN);
|
|
|
|
memcpy(dev_addr->src_dev_addr, IF_LLADDR(dev), dev->if_addrlen);
|
2008-05-05 18:35:55 +00:00
|
|
|
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;
|
2009-06-23 20:19:09 +00:00
|
|
|
int ret;
|
2008-05-05 18:35:55 +00:00
|
|
|
|
|
|
|
sin->sin_port = 0;
|
|
|
|
ifa = ifa_ifwithaddr(addr);
|
|
|
|
sin->sin_port = port;
|
|
|
|
if (!ifa)
|
|
|
|
return (EADDRNOTAVAIL);
|
2009-06-23 20:19:09 +00:00
|
|
|
ret = rdma_copy_addr(dev_addr, ifa->ifa_ifp, NULL);
|
|
|
|
ifa_free(ifa);
|
|
|
|
return (ret);
|
2008-05-05 18:35:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
This main goals of this project are:
1. separating L2 tables (ARP, NDP) from the L3 routing tables
2. removing as much locking dependencies among these layers as
possible to allow for some parallelism in the search operations
3. simplify the logic in the routing code,
The most notable end result is the obsolescent of the route
cloning (RTF_CLONING) concept, which translated into code reduction
in both IPv4 ARP and IPv6 NDP related modules, and size reduction in
struct rtentry{}. The change in design obsoletes the semantics of
RTF_CLONING, RTF_WASCLONE and RTF_LLINFO routing flags. The userland
applications such as "arp" and "ndp" have been modified to reflect
those changes. The output from "netstat -r" shows only the routing
entries.
Quite a few developers have contributed to this project in the
past: Glebius Smirnoff, Luigi Rizzo, Alessandro Cerri, and
Andre Oppermann. And most recently:
- Kip Macy revised the locking code completely, thus completing
the last piece of the puzzle, Kip has also been conducting
active functional testing
- Sam Leffler has helped me improving/refactoring the code, and
provided valuable reviews
- Julian Elischer setup the perforce tree for me and has helped
me maintaining that branch before the svn conversion
2008-12-15 06:10:57 +00:00
|
|
|
struct llentry *lle;
|
2008-05-05 18:35:55 +00:00
|
|
|
|
|
|
|
bzero(&iproute, sizeof iproute);
|
|
|
|
*dst = *dst_in;
|
|
|
|
|
|
|
|
rtalloc(&iproute);
|
2012-09-22 17:41:56 +00:00
|
|
|
if (iproute.ro_rt == NULL)
|
2008-05-05 18:35:55 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
arpresolve(iproute.ro_rt->rt_ifp, iproute.ro_rt, NULL,
|
This main goals of this project are:
1. separating L2 tables (ARP, NDP) from the L3 routing tables
2. removing as much locking dependencies among these layers as
possible to allow for some parallelism in the search operations
3. simplify the logic in the routing code,
The most notable end result is the obsolescent of the route
cloning (RTF_CLONING) concept, which translated into code reduction
in both IPv4 ARP and IPv6 NDP related modules, and size reduction in
struct rtentry{}. The change in design obsoletes the semantics of
RTF_CLONING, RTF_WASCLONE and RTF_LLINFO routing flags. The userland
applications such as "arp" and "ndp" have been modified to reflect
those changes. The output from "netstat -r" shows only the routing
entries.
Quite a few developers have contributed to this project in the
past: Glebius Smirnoff, Luigi Rizzo, Alessandro Cerri, and
Andre Oppermann. And most recently:
- Kip Macy revised the locking code completely, thus completing
the last piece of the puzzle, Kip has also been conducting
active functional testing
- Sam Leffler has helped me improving/refactoring the code, and
provided valuable reviews
- Julian Elischer setup the perforce tree for me and has helped
me maintaining that branch before the svn conversion
2008-12-15 06:10:57 +00:00
|
|
|
rt_key(iproute.ro_rt), dmac, &lle);
|
2008-05-05 18:35:55 +00:00
|
|
|
|
|
|
|
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];
|
This main goals of this project are:
1. separating L2 tables (ARP, NDP) from the L3 routing tables
2. removing as much locking dependencies among these layers as
possible to allow for some parallelism in the search operations
3. simplify the logic in the routing code,
The most notable end result is the obsolescent of the route
cloning (RTF_CLONING) concept, which translated into code reduction
in both IPv4 ARP and IPv6 NDP related modules, and size reduction in
struct rtentry{}. The change in design obsoletes the semantics of
RTF_CLONING, RTF_WASCLONE and RTF_LLINFO routing flags. The userland
applications such as "arp" and "ndp" have been modified to reflect
those changes. The output from "netstat -r" shows only the routing
entries.
Quite a few developers have contributed to this project in the
past: Glebius Smirnoff, Luigi Rizzo, Alessandro Cerri, and
Andre Oppermann. And most recently:
- Kip Macy revised the locking code completely, thus completing
the last piece of the puzzle, Kip has also been conducting
active functional testing
- Sam Leffler has helped me improving/refactoring the code, and
provided valuable reviews
- Julian Elischer setup the perforce tree for me and has helped
me maintaining that branch before the svn conversion
2008-12-15 06:10:57 +00:00
|
|
|
struct llentry *lle;
|
2008-05-05 18:35:55 +00:00
|
|
|
|
|
|
|
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,
|
2012-06-19 07:34:13 +00:00
|
|
|
(struct sockaddr *)dst_in, dmac, &lle);
|
2008-05-05 18:35:55 +00:00
|
|
|
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,
|
2012-10-10 08:36:38 +00:00
|
|
|
0
|
2008-05-05 18:35:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_VERSION(rdma_addr, 1);
|
|
|
|
DECLARE_MODULE(rdma_addr, mod_data, SI_SUB_EXEC, SI_ORDER_ANY);
|