/* * iSCSI Netlink/Linux Interface * * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman * Copyright (C) 2006 Mike Christie * Copyright (C) 2006 Red Hat, Inc. All rights reserved. * maintained by open-iscsi@googlegroups.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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * See the file COPYING included with this distribution for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "types.h" #include "iscsi_if.h" #include "log.h" #include "iscsi_ipc.h" #include "initiator.h" #include "iscsi_sysfs.h" #include "transport.h" #include "iscsi_netlink.h" #include "iscsi_err.h" #include "iscsi_timer.h" static int ctrl_fd; static struct sockaddr_nl src_addr, dest_addr; static void *xmitbuf = NULL; static int xmitlen = 0; static void *recvbuf = NULL; static int recvlen = 0; static void *nlm_sendbuf; static void *nlm_recvbuf; static void *pdu_sendbuf; static void *setparam_buf; static struct iscsi_ipc_ev_clbk *ipc_ev_clbk; static int ctldev_handle(void); #define NLM_BUF_DEFAULT_MAX (NLMSG_SPACE(ISCSI_DEF_MAX_RECV_SEG_LEN + \ sizeof(struct iscsi_uevent) + \ sizeof(struct iscsi_hdr))) #define PDU_SENDBUF_DEFAULT_MAX (ISCSI_DEF_MAX_RECV_SEG_LEN + \ sizeof(struct iscsi_uevent) + \ sizeof(struct iscsi_hdr)) #define NLM_SETPARAM_DEFAULT_MAX (NI_MAXHOST + 1 + sizeof(struct iscsi_uevent)) struct iscsi_ping_event { uint32_t host_no; uint32_t pid; int32_t status; int active; }; struct iscsi_ping_event ping_event; struct nlattr *iscsi_nla_alloc(uint16_t type, uint16_t len) { struct nlattr *attr; attr = calloc(1, ISCSI_NLA_TOTAL_LEN(len)); if (!attr) return NULL; attr->nla_len = ISCSI_NLA_LEN(len); attr->nla_type = type; return attr; } static int kread(char *data, int count) { log_debug(7, "in %s %u %u %p %p", __FUNCTION__, recvlen, count, data, recvbuf); memcpy(data, recvbuf + recvlen, count); recvlen += count; return count; } static int nl_read(int ctrl_fd, char *data, int size, int flags) { int rc; struct iovec iov; struct msghdr msg; log_debug(7, "in %s", __FUNCTION__); iov.iov_base = data; iov.iov_len = size; memset(&msg, 0, sizeof(msg)); msg.msg_name= (void*)&src_addr; msg.msg_namelen = sizeof(src_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; rc = recvmsg(ctrl_fd, &msg, flags); return rc; } static int nlpayload_read(int ctrl_fd, char *data, int count, int flags) { int rc; struct iovec iov; struct msghdr msg; log_debug(7, "in %s", __FUNCTION__); iov.iov_base = nlm_recvbuf; iov.iov_len = NLMSG_SPACE(count); if (iov.iov_len > NLM_BUF_DEFAULT_MAX) { log_error("Cannot read %lu bytes. nlm_recvbuf too small.", iov.iov_len); return -1; } memset(iov.iov_base, 0, iov.iov_len); memset(&msg, 0, sizeof(msg)); msg.msg_name= (void*)&src_addr; msg.msg_namelen = sizeof(src_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; /* * Netlink recvmsg call path: * * - transport api callback * - iscsi_control_conn_error (should succeed) * - iscsi_unicast_skb (must succeed) * - netlink_unicast (must succeed) * - netlink_data_ready (must succeed) * - netlink_sendskb (must succeed) * - netlink_recvmsg (must succeed) * - sock_recvmsg (must succeed) * - sys_recvmsg (must succeed) * - sys_socketcall (must succeed) * - syscall_call (must succeed) * * Note1: "must succeed" means succeed unless bug in daemon. * It also means - no sleep and memory allocation on * the path. * * Note2: "should succeed" means will succeed in most of cases * because of mempool preallocation. * * FIXME: if "Note2" than interface should generate iSCSI error * level 0 on its own. Interface must always succeed on this. */ rc = recvmsg(ctrl_fd, &msg, flags); if (data) memcpy(data, NLMSG_DATA(iov.iov_base), count); return rc; } static int kwritev(enum iscsi_uevent_e type, struct iovec *iovp, int count) { int i, rc; struct nlmsghdr *nlh; struct msghdr msg; int datalen = 0; log_debug(7, "in %s", __FUNCTION__); for (i = 0; i < count; i++) { datalen += iovp[i].iov_len; } if (xmitbuf && type != ISCSI_UEVENT_SEND_PDU) { for (i = 0; i < count; i++) { memcpy(xmitbuf + xmitlen, iovp[i].iov_base, iovp[i].iov_len); xmitlen += iovp[i].iov_len; } return datalen; } nlh = nlm_sendbuf; memset(nlh, 0, NLMSG_SPACE(0)); datalen = 0; for (i = 1; i < count; i++) datalen += iovp[i].iov_len; nlh->nlmsg_len = datalen + sizeof(*nlh); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; nlh->nlmsg_type = type; iovp[0].iov_base = (void *)nlh; iovp[0].iov_len = sizeof(*nlh); memset(&msg, 0, sizeof(msg)); msg.msg_name= (void*)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = iovp; msg.msg_iovlen = count; do { /* * Netlink down call path: * * - transport api call * - iscsi_if_recv_msg (must succeed) * - iscsi_if_rx (must succeed) * - netlink_data_ready (must succeed) * - netlink_sendskb (must succeed) * - netlink_sendmsg (alloc_skb() might fail) * - sock_sendmsg (must succeed) * - sys_sendmsg (must succeed) * - sys_socketcall (must succeed) * - syscall_call (must succeed) * * Note1: "must succeed" means succeed unless bug in daemon. * It also means - no sleep and memory allocation on * the path. * * Note2: netlink_sendmsg() might fail because of OOM. Since * we are in user-space, we will sleep until we succeed. */ rc = sendmsg(ctrl_fd, &msg, 0); if (rc == -ENOMEM) { log_debug(1, "sendmsg: alloc_skb() failed"); sleep(1); } else if (rc < 0) { log_error("sendmsg: bug? ctrl_fd %d", ctrl_fd); exit(rc); } } while (rc < 0); return rc; } /* * __kipc_call() should never block. Therefore * Netlink's xmit logic is serialized. This means we do not allocate on * xmit path. Instead we reuse nlm_sendbuf buffer. * * Transport must assure non-blocking operations for: * * - session_create() * - conn_create() * - conn_bind() * _ set_param() * - conn_start() * - conn_stop() * * Its OK to block for cleanup for short period of time in operatations for: * * - conn_destroy() * - session_destroy() * * FIXME: interface needs to be extended to allow longer blocking on * cleanup. (Dima) */ static int __kipc_call(struct iovec *iovp, int count) { int rc, iferr; struct iscsi_uevent *ev = iovp[1].iov_base; enum iscsi_uevent_e type = ev->type; log_debug(7, "in %s", __FUNCTION__); rc = kwritev(type, iovp, count); do { if ((rc = nlpayload_read(ctrl_fd, (void*)ev, sizeof(*ev), MSG_PEEK)) < 0) { return rc; } if (ev->type != type) { log_debug(1, "expecting event %d, got %d, handling...", type, ev->type); if (ev->type == ISCSI_KEVENT_IF_ERROR) { if ((rc = nlpayload_read(ctrl_fd, (void*)ev, sizeof(*ev), 0)) < 0) { return rc; } /* * iferror is u32, but the kernel returns * negative errno values for errors. */ iferr = ev->iferror; if (iferr == -ENOSYS) /* not fatal so let caller handle log */ log_debug(1, "Received iferror %d: %s.", iferr, strerror(-iferr)); else if (iferr < 0) log_error("Received iferror %d: %s.", iferr, strerror(-iferr)); else log_error("Received iferror %d.", iferr); return ev->iferror; } /* * receive and queue async. event which as of * today could be: * - CONN_ERROR * - RECV_PDU */ ctldev_handle(); } else if (ev->type == ISCSI_UEVENT_GET_STATS) { /* kget_stats() will read */ return 0; } else if (ev->type == ISCSI_UEVENT_GET_CHAP) { /* kget_chap() will read */ return 0; } else if (ev->type == ISCSI_UEVENT_GET_HOST_STATS) { /* kget_host_stats() will read */ return 0; } else { if ((rc = nlpayload_read(ctrl_fd, (void*)ev, sizeof(*ev), 0)) < 0) { return rc; } break; } } while (ev->type != type); return rc; } static int ksendtargets(uint64_t transport_handle, uint32_t host_no, struct sockaddr *addr) { int rc, addrlen; struct iscsi_uevent *ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); ev = (struct iscsi_uevent *)setparam_buf; ev->type = ISCSI_UEVENT_TGT_DSCVR; ev->transport_handle = transport_handle; ev->u.tgt_dscvr.type = ISCSI_TGT_DSCVR_SEND_TARGETS; ev->u.tgt_dscvr.host_no = host_no; if (addr->sa_family == PF_INET) addrlen = sizeof(struct sockaddr_in); else if (addr->sa_family == PF_INET6) addrlen = sizeof(struct sockaddr_in6); else { log_error("%s unknown addr family %d", __FUNCTION__, addr->sa_family); return -EINVAL; } memcpy(setparam_buf + sizeof(*ev), addr, addrlen); iov[1].iov_base = ev; iov[1].iov_len = sizeof(*ev) + addrlen; rc = __kipc_call(iov, 2); if (rc < 0) { log_error("sendtargets failed rc%d", rc); return rc; } return 0; } static int kcreate_session(uint64_t transport_handle, uint64_t ep_handle, uint32_t initial_cmdsn, uint16_t cmds_max, uint16_t qdepth, uint32_t *out_sid, uint32_t *hostno) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); if (ep_handle == 0) { ev.type = ISCSI_UEVENT_CREATE_SESSION; ev.transport_handle = transport_handle; ev.u.c_session.initial_cmdsn = initial_cmdsn; ev.u.c_session.cmds_max = cmds_max; ev.u.c_session.queue_depth = qdepth; } else { ev.type = ISCSI_UEVENT_CREATE_BOUND_SESSION; ev.transport_handle = transport_handle; ev.u.c_bound_session.initial_cmdsn = initial_cmdsn; ev.u.c_bound_session.cmds_max = cmds_max; ev.u.c_bound_session.queue_depth = qdepth; ev.u.c_bound_session.ep_handle = ep_handle; } iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; *hostno = ev.r.c_session_ret.host_no; *out_sid = ev.r.c_session_ret.sid; return 0; } static int kdestroy_session(uint64_t transport_handle, uint32_t sid) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_DESTROY_SESSION; ev.transport_handle = transport_handle; ev.u.d_session.sid = sid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kunbind_session(uint64_t transport_handle, uint32_t sid) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_UNBIND_SESSION; ev.transport_handle = transport_handle; ev.u.d_session.sid = sid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kcreate_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, uint32_t *out_cid) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_CREATE_CONN; ev.transport_handle = transport_handle; ev.u.c_conn.cid = cid; ev.u.c_conn.sid = sid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) { log_debug(7, "returned %d", rc); return rc; } if ((int)ev.r.c_conn_ret.cid == -1) return -EIO; *out_cid = ev.r.c_conn_ret.cid; return 0; } static int kdestroy_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_DESTROY_CONN; ev.transport_handle = transport_handle; ev.u.d_conn.sid = sid; ev.u.d_conn.cid = cid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kbind_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, uint64_t transport_eph, int is_leading, int *retcode) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_BIND_CONN; ev.transport_handle = transport_handle; ev.u.b_conn.sid = sid; ev.u.b_conn.cid = cid; ev.u.b_conn.transport_eph = transport_eph; ev.u.b_conn.is_leading = is_leading; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; *retcode = ev.r.retcode; return 0; } static void ksend_pdu_begin(uint64_t transport_handle, uint32_t sid, uint32_t cid, int hdr_size, int data_size) { struct iscsi_uevent *ev; int total_xmitlen = sizeof(*ev) + hdr_size + data_size; log_debug(7, "in %s", __FUNCTION__); if (xmitbuf) { log_error("send's begin state machine bug?"); exit(-EIO); } if (total_xmitlen > PDU_SENDBUF_DEFAULT_MAX) { log_error("BUG: Cannot send %d bytes.", total_xmitlen); exit(-EINVAL); } xmitbuf = pdu_sendbuf; memset(xmitbuf, 0, total_xmitlen); xmitlen = sizeof(*ev); ev = xmitbuf; memset(ev, 0, sizeof(*ev)); ev->type = ISCSI_UEVENT_SEND_PDU; ev->transport_handle = transport_handle; ev->u.send_pdu.sid = sid; ev->u.send_pdu.cid = cid; ev->u.send_pdu.hdr_size = hdr_size; ev->u.send_pdu.data_size = data_size; log_debug(3, "send PDU began for hdr %d bytes and data %d bytes", hdr_size, data_size); } static int ksend_pdu_end(uint64_t transport_handle, uint32_t sid, uint32_t cid, int *retcode) { int rc; struct iscsi_uevent *ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); if (!xmitbuf) { log_error("send's end state machine bug?"); exit(-EIO); } ev = xmitbuf; if (ev->u.send_pdu.sid != sid || ev->u.send_pdu.cid != cid) { log_error("send's end state machine corruption?"); exit(-EIO); } iov[1].iov_base = xmitbuf; iov[1].iov_len = xmitlen; rc = __kipc_call(iov, 2); if (rc < 0) goto err; if (ev->r.retcode) { *retcode = ev->r.retcode; goto err; } if (ev->type != ISCSI_UEVENT_SEND_PDU) { log_error("bad event: bug on send_pdu_end?"); exit(-EIO); } log_debug(3, "send PDU finished for conn %d:%d", sid, cid); xmitbuf = NULL; return 0; err: xmitbuf = NULL; xmitlen = 0; return rc; } static int kset_host_param(uint64_t transport_handle, uint32_t host_no, enum iscsi_host_param param, void *value, int type) { struct iscsi_uevent *ev; char *param_str; int rc, len; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); ev = (struct iscsi_uevent *)setparam_buf; ev->type = ISCSI_UEVENT_SET_HOST_PARAM; ev->transport_handle = transport_handle; ev->u.set_host_param.host_no = host_no; ev->u.set_host_param.param = param; param_str = setparam_buf + sizeof(*ev); switch (type) { case ISCSI_INT: sprintf(param_str, "%d", *((int *)value)); break; case ISCSI_STRING: if (!strlen(value)) return 0; sprintf(param_str, "%s", (char *)value); break; default: log_error("invalid type %d", type); return -EINVAL; } ev->u.set_host_param.len = len = strlen(param_str) + 1; iov[1].iov_base = ev; iov[1].iov_len = sizeof(*ev) + len; rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kset_param(uint64_t transport_handle, uint32_t sid, uint32_t cid, enum iscsi_param param, void *value, int type) { struct iscsi_uevent *ev; char *param_str; int rc, len; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); ev = (struct iscsi_uevent *)setparam_buf; ev->type = ISCSI_UEVENT_SET_PARAM; ev->transport_handle = transport_handle; ev->u.set_param.sid = sid; ev->u.set_param.cid = cid; ev->u.set_param.param = param; param_str = setparam_buf + sizeof(*ev); switch (type) { case ISCSI_INT: sprintf(param_str, "%d", *((int *)value)); break; case ISCSI_UINT: sprintf(param_str, "%u", *((unsigned int *)value)); break; case ISCSI_STRING: if (!strlen(value)) return 0; sprintf(param_str, "%s", (char *)value); break; default: log_error("invalid type %d", type); return -EINVAL; } ev->u.set_param.len = len = strlen(param_str) + 1; iov[1].iov_base = ev; iov[1].iov_len = sizeof(*ev) + len; rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kstop_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, int flag) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_STOP_CONN; ev.transport_handle = transport_handle; ev.u.stop_conn.sid = sid; ev.u.stop_conn.cid = cid; ev.u.stop_conn.flag = flag; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kstart_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, int *retcode) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_START_CONN; ev.transport_handle = transport_handle; ev.u.start_conn.sid = sid; ev.u.start_conn.cid = cid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; *retcode = ev.r.retcode; return 0; } static int krecv_pdu_begin(struct iscsi_conn *conn) { int rc; log_debug(7, "in %s", __FUNCTION__); if (recvbuf) { log_error("recv's begin state machine bug?"); return -EIO; } if (!conn->recv_context) { rc = ipc->ctldev_handle(); if (rc == -ENXIO) /* event for some other conn */ return -EAGAIN; else if (rc < 0) /* fatal handling error or conn error */ return rc; /* * Session create/destroy event for another conn */ if (!conn->recv_context) return -EAGAIN; } recvbuf = conn->recv_context->data + sizeof(struct iscsi_uevent); recvlen = 0; log_debug(3, "recv PDU began, pdu handle %p", recvbuf); return 0; } static int krecv_pdu_end(struct iscsi_conn *conn) { log_debug(7, "in %s", __FUNCTION__); if (!recvbuf) { log_error("recv's end state machine bug?"); return -EIO; } log_debug(3, "recv PDU finished for pdu handle 0x%p", recvbuf); ipc_ev_clbk->put_ev_context(conn->recv_context); conn->recv_context = NULL; recvbuf = NULL; return 0; } int ktransport_ep_connect(iscsi_conn_t *conn, int non_blocking) { int rc, addrlen; struct iscsi_uevent *ev; struct sockaddr *dst_addr = (struct sockaddr *)&conn->saddr; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); ev = (struct iscsi_uevent *)setparam_buf; ev->transport_handle = conn->session->t->handle; if (conn->bind_ep) { ev->type = ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST; ev->u.ep_connect_through_host.non_blocking = non_blocking; ev->u.ep_connect_through_host.host_no = conn->session->hostno; } else { ev->type = ISCSI_UEVENT_TRANSPORT_EP_CONNECT; ev->u.ep_connect.non_blocking = non_blocking; } if (dst_addr->sa_family == PF_INET) addrlen = sizeof(struct sockaddr_in); else if (dst_addr->sa_family == PF_INET6) addrlen = sizeof(struct sockaddr_in6); else { log_error("%s unknown addr family %d", __FUNCTION__, dst_addr->sa_family); return -EINVAL; } memcpy(setparam_buf + sizeof(*ev), dst_addr, addrlen); iov[1].iov_base = ev; iov[1].iov_len = sizeof(*ev) + addrlen; rc = __kipc_call(iov, 2); if (rc < 0) return rc; if (!ev->r.ep_connect_ret.handle) return -EIO; conn->transport_ep_handle = ev->r.ep_connect_ret.handle; log_debug(6, "%s got handle %llx", __FUNCTION__, (unsigned long long)conn->transport_ep_handle); return 0; } int ktransport_ep_poll(iscsi_conn_t *conn, int timeout_ms) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_TRANSPORT_EP_POLL; ev.transport_handle = conn->session->t->handle; ev.u.ep_poll.ep_handle = conn->transport_ep_handle; ev.u.ep_poll.timeout_ms = timeout_ms; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return ev.r.retcode; } void ktransport_ep_disconnect(iscsi_conn_t *conn) { int rc; struct iscsi_uevent ev; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); if (conn->transport_ep_handle == -1) return; memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT; ev.transport_handle = conn->session->t->handle; ev.u.ep_disconnect.ep_handle = conn->transport_ep_handle; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) { log_error("connection %d:%d transport disconnect failed for " "ep %" PRIu64 " with error %d.", conn->session->id, conn->id, conn->transport_ep_handle, rc); } else conn->transport_ep_handle = -1; } static int kget_stats(uint64_t transport_handle, uint32_t sid, uint32_t cid, char *statsbuf, int statsbuf_max) { int rc; int ev_size; struct iscsi_uevent ev; char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; struct nlmsghdr *nlh; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_GET_STATS; ev.transport_handle = transport_handle; ev.u.get_stats.sid = sid; ev.u.get_stats.cid = cid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; if ((rc = nl_read(ctrl_fd, nlm_ev, NLMSG_SPACE(sizeof(struct iscsi_uevent)), MSG_PEEK)) < 0) { log_error("can not read nlm_ev, error %d", rc); return rc; } nlh = (struct nlmsghdr *)nlm_ev; ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); log_debug(6, "message real length is %d bytes", nlh->nlmsg_len); if (ev_size > statsbuf_max) { log_error("destanation buffer for statistics is " "not big enough to fit %d bytes", statsbuf_max); ev_size = statsbuf_max; } if ((rc = nlpayload_read(ctrl_fd, (void*)statsbuf, ev_size, 0)) < 0) { log_error("can not read from NL socket, error %d", rc); return rc; } return 0; } static int kset_net_config(uint64_t transport_handle, uint32_t host_no, struct iovec *iovs, uint32_t param_count) { struct iscsi_uevent ev; int rc, ev_len; struct iovec *iov = iovs + 1; log_debug(8, "in %s", __FUNCTION__); ev_len = sizeof(ev); ev.type = ISCSI_UEVENT_SET_IFACE_PARAMS; ev.transport_handle = transport_handle; ev.u.set_iface_params.host_no = host_no; /* first two iovs for nlmsg hdr and ev */ ev.u.set_iface_params.count = param_count - 2; iov->iov_base = &ev; iov->iov_len = ev_len; rc = __kipc_call(iovs, param_count); if (rc < 0) return rc; return 0; } static int krecv_conn_state(struct iscsi_conn *conn, uint32_t *state) { int rc; rc = ipc->ctldev_handle(); if (rc == -ENXIO) { /* event for some other conn */ rc = -EAGAIN; goto exit; } else if (rc < 0) /* fatal handling error or conn error */ goto exit; /* unexpected event without a receive context */ if (!conn->recv_context) return -EAGAIN; *state = *(enum iscsi_conn_state *)conn->recv_context->data; ipc_ev_clbk->put_ev_context(conn->recv_context); conn->recv_context = NULL; exit: return rc; } static int ksend_ping(uint64_t transport_handle, uint32_t host_no, struct sockaddr *addr, uint32_t iface_num, uint32_t iface_type, uint32_t pid, uint32_t size) { int rc, addrlen; struct iscsi_uevent *ev; struct iovec iov[2]; log_debug(8, "in %s", __FUNCTION__); memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); ev = (struct iscsi_uevent *)setparam_buf; ev->type = ISCSI_UEVENT_PING; ev->transport_handle = transport_handle; ev->u.iscsi_ping.host_no = host_no; ev->u.iscsi_ping.iface_num = iface_num; ev->u.iscsi_ping.iface_type = iface_type; ev->u.iscsi_ping.payload_size = size; ev->u.iscsi_ping.pid = pid; if (addr->sa_family == PF_INET) addrlen = sizeof(struct sockaddr_in); else if (addr->sa_family == PF_INET6) addrlen = sizeof(struct sockaddr_in6); else { log_error("%s unknown addr family %d", __FUNCTION__, addr->sa_family); return -EINVAL; } memcpy(setparam_buf + sizeof(*ev), addr, addrlen); iov[1].iov_base = ev; iov[1].iov_len = sizeof(*ev) + addrlen; rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kexec_ping(uint64_t transport_handle, uint32_t host_no, struct sockaddr *addr, uint32_t iface_num, uint32_t iface_type, uint32_t size, uint32_t *status) { struct pollfd pfd; struct timeval ping_timer; int timeout, fd, rc; uint32_t pid; *status = 0; fd = ipc->ctldev_open(); if (fd < 0) { log_error("Could not open netlink socket."); return ISCSI_ERR; } /* prepare to poll */ memset(&pfd, 0, sizeof(pfd)); pfd.fd = fd; pfd.events = POLLIN | POLLPRI; /* get unique ping id */ pid = rand(); rc = ksend_ping(transport_handle, host_no, addr, iface_num, iface_type, pid, size); if (rc != 0) { switch (rc) { case -ENOSYS: rc = ISCSI_ERR_OP_NOT_SUPP; break; case -EINVAL: rc = ISCSI_ERR_INVAL; break; default: rc = ISCSI_ERR; } goto close_nl; } ping_event.host_no = -1; ping_event.pid = -1; ping_event.status = -1; ping_event.active = -1; iscsi_timer_set(&ping_timer, 30); timeout = iscsi_timer_msecs_until(&ping_timer); while (1) { pfd.revents = 0; rc = poll(&pfd, 1, timeout); if (iscsi_timer_expired(&ping_timer)) { rc = ISCSI_ERR_TRANS_TIMEOUT; break; } if (rc > 0) { if (pfd.revents & (POLLIN | POLLPRI)) { timeout = iscsi_timer_msecs_until(&ping_timer); rc = ipc->ctldev_handle(); if (ping_event.active != 1) continue; if (pid != ping_event.pid) continue; rc = 0; *status = ping_event.status; break; } if (pfd.revents & POLLHUP) { rc = ISCSI_ERR_TRANS; break; } if (pfd.revents & POLLNVAL) { rc = ISCSI_ERR_INTERNAL; break; } if (pfd.revents & POLLERR) { rc = ISCSI_ERR_INTERNAL; break; } } else if (rc < 0) { rc = ISCSI_ERR_INTERNAL; break; } } close_nl: ipc->ctldev_close(); return rc; } static int kget_chap(uint64_t transport_handle, uint32_t host_no, uint16_t chap_tbl_idx, uint32_t num_entries, char *chap_buf, uint32_t *valid_chap_entries) { int rc = 0; int ev_size; struct iscsi_uevent ev; struct iovec iov[2]; char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; struct nlmsghdr *nlh; memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_GET_CHAP; ev.transport_handle = transport_handle; ev.u.get_chap.host_no = host_no; ev.u.get_chap.chap_tbl_idx = chap_tbl_idx; ev.u.get_chap.num_entries = num_entries; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; if ((rc = nl_read(ctrl_fd, nlm_ev, NLMSG_SPACE(sizeof(struct iscsi_uevent)), MSG_PEEK)) < 0) { log_error("can not read nlm_ev, error %d", rc); return rc; } nlh = (struct nlmsghdr *)nlm_ev; ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); if ((rc = nlpayload_read(ctrl_fd, (void *)chap_buf, ev_size, 0)) < 0) { log_error("can not read from NL socket, error %d", rc); return rc; } *valid_chap_entries = ev.u.get_chap.num_entries; return rc; } static int kset_chap(uint64_t transport_handle, uint32_t host_no, struct iovec *iovs, uint32_t param_count) { int rc; struct iscsi_uevent ev; struct iovec *iov = iovs + 1; log_debug(8, "in %s", __func__); ev.type = ISCSI_UEVENT_SET_CHAP; ev.transport_handle = transport_handle; ev.u.set_path.host_no = host_no; iov->iov_base = &ev; iov->iov_len = sizeof(ev); rc = __kipc_call(iovs, param_count); if (rc < 0) return rc; return 0; } static int kdelete_chap(uint64_t transport_handle, uint32_t host_no, uint16_t chap_tbl_idx) { int rc = 0; struct iscsi_uevent ev; struct iovec iov[2]; memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_DELETE_CHAP; ev.transport_handle = transport_handle; ev.u.delete_chap.host_no = host_no; ev.u.delete_chap.chap_tbl_idx = chap_tbl_idx; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return rc; } static int kset_flashnode_params(uint64_t transport_handle, uint32_t host_no, uint32_t flashnode_idx, struct iovec *iovs, uint32_t param_count) { struct iscsi_uevent ev; int rc, ev_len; struct iovec *iov = iovs + 1; log_debug(8, "in %s", __FUNCTION__); ev_len = sizeof(ev); ev.type = ISCSI_UEVENT_SET_FLASHNODE_PARAMS; ev.transport_handle = transport_handle; ev.u.set_flashnode.host_no = host_no; ev.u.set_flashnode.flashnode_idx = flashnode_idx; /* first two iovs for nlmsg hdr and ev */ ev.u.set_flashnode.count = param_count - 2; iov->iov_base = &ev; iov->iov_len = ev_len; rc = __kipc_call(iovs, param_count); if (rc < 0) return rc; return 0; } static int knew_flashnode(uint64_t transport_handle, uint32_t host_no, void *value, uint32_t *flashnode_idx) { struct iscsi_uevent *ev; char *param_str; int rc, len; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); ev = (struct iscsi_uevent *)setparam_buf; ev->type = ISCSI_UEVENT_NEW_FLASHNODE; ev->transport_handle = transport_handle; ev->u.new_flashnode.host_no = host_no; param_str = setparam_buf + sizeof(*ev); if (!strlen(value)) return 0; sprintf(param_str, "%s", (char *)value); len = strlen(param_str) + 1; ev->u.new_flashnode.len = len; iov[1].iov_base = ev; iov[1].iov_len = sizeof(*ev) + len; rc = __kipc_call(iov, 2); if (rc < 0) return rc; *flashnode_idx = ev->r.new_flashnode_ret.flashnode_idx; return 0; } static int kdel_flashnode(uint64_t transport_handle, uint32_t host_no, uint32_t flashnode_idx) { struct iscsi_uevent ev; int rc; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_DEL_FLASHNODE; ev.transport_handle = transport_handle; ev.u.del_flashnode.host_no = host_no; ev.u.del_flashnode.flashnode_idx = flashnode_idx; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int klogin_flashnode(uint64_t transport_handle, uint32_t host_no, uint32_t flashnode_idx) { struct iscsi_uevent ev; int rc; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_LOGIN_FLASHNODE; ev.transport_handle = transport_handle; ev.u.login_flashnode.host_no = host_no; ev.u.login_flashnode.flashnode_idx = flashnode_idx; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int klogout_flashnode(uint64_t transport_handle, uint32_t host_no, uint32_t flashnode_idx) { struct iscsi_uevent ev; int rc; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_LOGOUT_FLASHNODE; ev.transport_handle = transport_handle; ev.u.logout_flashnode.host_no = host_no; ev.u.logout_flashnode.flashnode_idx = flashnode_idx; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int klogout_flashnode_sid(uint64_t transport_handle, uint32_t host_no, uint32_t sid) { struct iscsi_uevent ev; int rc; struct iovec iov[2]; log_debug(7, "in %s", __FUNCTION__); memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_LOGOUT_FLASHNODE_SID; ev.transport_handle = transport_handle; ev.u.logout_flashnode_sid.host_no = host_no; ev.u.logout_flashnode_sid.sid = sid; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; return 0; } static int kget_host_stats(uint64_t transport_handle, uint32_t host_no, char *host_stats) { int rc = 0; int ev_size; struct iscsi_uevent ev; struct iovec iov[2]; char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; struct nlmsghdr *nlh; memset(&ev, 0, sizeof(struct iscsi_uevent)); ev.type = ISCSI_UEVENT_GET_HOST_STATS; ev.transport_handle = transport_handle; ev.u.get_host_stats.host_no = host_no; iov[1].iov_base = &ev; iov[1].iov_len = sizeof(ev); rc = __kipc_call(iov, 2); if (rc < 0) return rc; if ((rc = nl_read(ctrl_fd, nlm_ev, NLMSG_SPACE(sizeof(struct iscsi_uevent)), MSG_PEEK)) < 0) { log_error("can not read nlm_ev, error %d", rc); return rc; } nlh = (struct nlmsghdr *)nlm_ev; ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); if ((rc = nlpayload_read(ctrl_fd, (void *)host_stats, ev_size, 0)) < 0) { log_error("can not read from NL socket, error %d", rc); return rc; } return rc; } static void drop_data(struct nlmsghdr *nlh) { int ev_size; ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); nlpayload_read(ctrl_fd, NULL, ev_size, 0); } static int ctldev_handle(void) { int rc, ev_size; struct iscsi_uevent *ev; iscsi_session_t *session = NULL; iscsi_conn_t *conn = NULL; char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; struct nlmsghdr *nlh; struct iscsi_ev_context *ev_context; uint32_t sid = 0, cid = 0; log_debug(7, "in %s", __FUNCTION__); if ((rc = nl_read(ctrl_fd, nlm_ev, NLMSG_SPACE(sizeof(struct iscsi_uevent)), MSG_PEEK)) < 0) { log_error("can not read nlm_ev, error %d", rc); return rc; } nlh = (struct nlmsghdr *)nlm_ev; ev = (struct iscsi_uevent *)NLMSG_DATA(nlm_ev); log_debug(7, "%s got event type %u", __FUNCTION__, ev->type); /* drivers like qla4xxx can be inserted after iscsid is started */ switch (ev->type) { case ISCSI_KEVENT_CREATE_SESSION: /* old kernels sent ISCSI_UEVENT_CREATE_SESSION on creation */ case ISCSI_UEVENT_CREATE_SESSION: drop_data(nlh); if (!ipc_ev_clbk) return 0; if (ipc_ev_clbk->create_session) ipc_ev_clbk->create_session(ev->r.c_session_ret.host_no, ev->r.c_session_ret.sid); return 0; case ISCSI_KEVENT_DESTROY_SESSION: drop_data(nlh); if (!ipc_ev_clbk) return 0; if (ipc_ev_clbk->destroy_session) ipc_ev_clbk->destroy_session(ev->r.d_session.host_no, ev->r.d_session.sid); return 0; case ISCSI_KEVENT_RECV_PDU: sid = ev->r.recv_req.sid; cid = ev->r.recv_req.cid; break; case ISCSI_KEVENT_CONN_ERROR: sid = ev->r.connerror.sid; cid = ev->r.connerror.cid; break; case ISCSI_KEVENT_CONN_LOGIN_STATE: sid = ev->r.conn_login.sid; cid = ev->r.conn_login.cid; break; case ISCSI_KEVENT_UNBIND_SESSION: sid = ev->r.unbind_session.sid; /* session wide event so cid is 0 */ cid = 0; break; case ISCSI_KEVENT_HOST_EVENT: switch (ev->r.host_event.code) { case ISCSI_EVENT_LINKUP: log_warning("Host%u: Link Up.", ev->r.host_event.host_no); break; case ISCSI_EVENT_LINKDOWN: log_warning("Host%u: Link Down.", ev->r.host_event.host_no); break; default: log_debug(7, "Host%u: Unknown host event: %u.", ev->r.host_event.host_no, ev->r.host_event.code); } drop_data(nlh); return 0; case ISCSI_KEVENT_PING_COMP: ping_event.host_no = ev->r.ping_comp.host_no; ping_event.pid = ev->r.ping_comp.pid; ping_event.status = ev->r.ping_comp.status; ping_event.active = 1; drop_data(nlh); return 0; default: if ((ev->type > ISCSI_UEVENT_MAX && ev->type < KEVENT_BASE) || (ev->type > ISCSI_KEVENT_MAX)) log_error("Unknown kernel event %d. You may want to " " upgrade your iscsi tools.", ev->type); else /* * If another app is using the interface we might * see their * stuff. Just drop it. */ log_debug(7, "Got unknown event %d. Dropping.", ev->type); drop_data(nlh); return 0; } /* verify connection */ session = session_find_by_sid(sid); if (!session) { /* * this can happen normally when other apps are using the * nl interface. */ log_debug(1, "Could not verify connection %d:%d. Dropping " "event.", sid, cid); drop_data(nlh); return -ENXIO; } conn = &session->conn[0]; ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); ev_context = ipc_ev_clbk->get_ev_context(conn, ev_size); if (!ev_context) { log_error("Can not allocate memory for receive context."); drop_data(nlh); return -ENOMEM; } log_debug(6, "message real length is %d bytes, recv_handle %p", nlh->nlmsg_len, ev_context->data); if ((rc = nlpayload_read(ctrl_fd, ev_context->data, ev_size, 0)) < 0) { ipc_ev_clbk->put_ev_context(ev_context); log_error("can not read from NL socket, error %d", rc); /* retry later */ return rc; } /* * we sched these events because the handlers could call back * into ctldev_handle */ switch (ev->type) { case ISCSI_KEVENT_RECV_PDU: rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, EV_CONN_RECV_PDU); break; case ISCSI_KEVENT_CONN_ERROR: memcpy(ev_context->data, &ev->r.connerror.error, sizeof(ev->r.connerror.error)); rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, EV_CONN_ERROR); break; case ISCSI_KEVENT_CONN_LOGIN_STATE: memcpy(ev_context->data, &ev->r.conn_login.state, sizeof(ev->r.conn_login.state)); rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, EV_CONN_LOGIN); break; case ISCSI_KEVENT_UNBIND_SESSION: rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, EV_CONN_STOP); break; default: ipc_ev_clbk->put_ev_context(ev_context); log_error("unknown kernel event %d", ev->type); return -EEXIST; } if (rc) ipc_ev_clbk->put_ev_context(ev_context); return rc; } static int ctldev_open(void) { log_debug(7, "in %s", __FUNCTION__); nlm_sendbuf = calloc(1, NLM_BUF_DEFAULT_MAX); if (!nlm_sendbuf) { log_error("can not allocate nlm_sendbuf"); return -1; } nlm_recvbuf = calloc(1, NLM_BUF_DEFAULT_MAX); if (!nlm_recvbuf) { log_error("can not allocate nlm_recvbuf"); goto free_nlm_sendbuf; } pdu_sendbuf = calloc(1, PDU_SENDBUF_DEFAULT_MAX); if (!pdu_sendbuf) { log_error("can not allocate nlm_sendbuf"); goto free_nlm_recvbuf; } setparam_buf = calloc(1, NLM_SETPARAM_DEFAULT_MAX); if (!setparam_buf) { log_error("can not allocate setparam_buf"); goto free_pdu_sendbuf; } ctrl_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ISCSI); if (ctrl_fd < 0) { log_error("can not create NETLINK_ISCSI socket [%s]", strerror(errno)); goto free_setparam_buf; } memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); src_addr.nl_groups = 1; if (bind(ctrl_fd, (struct sockaddr *)&src_addr, sizeof(src_addr))) { log_error("can not bind NETLINK_ISCSI socket [%s]", strerror(errno)); goto close_socket; } memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; /* kernel */ dest_addr.nl_groups = 0; /* unicast */ log_debug(7, "created NETLINK_ISCSI socket..."); return ctrl_fd; close_socket: close(ctrl_fd); free_setparam_buf: free(setparam_buf); free_pdu_sendbuf: free(pdu_sendbuf); free_nlm_recvbuf: free(nlm_recvbuf); free_nlm_sendbuf: free(nlm_sendbuf); return -1; } static void ctldev_close(void) { log_debug(7, "in %s", __FUNCTION__); if (ctrl_fd >= 0) close(ctrl_fd); free(setparam_buf); free(pdu_sendbuf); free(nlm_recvbuf); free(nlm_sendbuf); } struct iscsi_ipc nl_ipc = { .name = "Open-iSCSI Kernel IPC/NETLINK v.1", .ctldev_bufmax = NLM_BUF_DEFAULT_MAX, .ctldev_open = ctldev_open, .ctldev_close = ctldev_close, .ctldev_handle = ctldev_handle, .sendtargets = ksendtargets, .create_session = kcreate_session, .destroy_session = kdestroy_session, .unbind_session = kunbind_session, .create_conn = kcreate_conn, .destroy_conn = kdestroy_conn, .bind_conn = kbind_conn, .set_param = kset_param, .set_host_param = kset_host_param, .get_param = NULL, .start_conn = kstart_conn, .stop_conn = kstop_conn, .get_stats = kget_stats, .writev = kwritev, .send_pdu_begin = ksend_pdu_begin, .send_pdu_end = ksend_pdu_end, .read = kread, .recv_pdu_begin = krecv_pdu_begin, .recv_pdu_end = krecv_pdu_end, .set_net_config = kset_net_config, .recv_conn_state = krecv_conn_state, .exec_ping = kexec_ping, .get_chap = kget_chap, .set_chap = kset_chap, .delete_chap = kdelete_chap, .set_flash_node_params = kset_flashnode_params, .new_flash_node = knew_flashnode, .del_flash_node = kdel_flashnode, .login_flash_node = klogin_flashnode, .logout_flash_node = klogout_flashnode, .logout_flash_node_sid = klogout_flashnode_sid, .get_host_stats = kget_host_stats, }; struct iscsi_ipc *ipc = &nl_ipc; void ipc_register_ev_callback(struct iscsi_ipc_ev_clbk *ev_clbk) { ipc_ev_clbk = ev_clbk; }