diff --git a/sbin/route/route_netlink.c b/sbin/route/route_netlink.c --- a/sbin/route/route_netlink.c +++ b/sbin/route/route_netlink.c @@ -39,8 +39,11 @@ void monitor_nl(int fib); struct nl_helper; -static void print_getmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct sockaddr *dst); -static void print_nlmsg(struct nl_helper *h, struct nlmsghdr *hdr); +struct snl_msg_info; +static void print_getmsg(struct nl_helper *h, struct nlmsghdr *hdr, + struct sockaddr *dst); +static void print_nlmsg(struct nl_helper *h, struct nlmsghdr *hdr, + struct snl_msg_info *cinfo); #define s6_addr32 __u6_addr.__u6_addr32 #define bitcount32(x) __bitcount32((uint32_t)(x)) @@ -417,9 +420,9 @@ snprintf(buf + sz, bufsize - sz, "/%d", plen); } - static int -print_line_prefix(const char *cmd, const char *name) +print_line_prefix(struct nlmsghdr *hdr, struct snl_msg_info *cinfo, + const char *cmd, const char *name) { struct timespec tp; struct tm tm; @@ -429,7 +432,8 @@ localtime_r(&tp.tv_sec, &tm); strftime(buf, sizeof(buf), "%T", &tm); - int len = printf("%s.%03ld %s %s ", buf, tp.tv_nsec / 1000000, cmd, name); + int len = printf("%s.%03ld PID %4u %s %s ", buf, tp.tv_nsec / 1000000, + cinfo->process_id, cmd, name); return (len); } @@ -481,7 +485,8 @@ } static void -print_nlmsg_route(struct nl_helper *h, struct nlmsghdr *hdr) +print_nlmsg_route(struct nl_helper *h, struct nlmsghdr *hdr, + struct snl_msg_info *cinfo) { struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT }; struct snl_state *ss = &h->ss_cmd; @@ -492,7 +497,7 @@ // 20:19:41.333 add route 10.0.0.0/24 gw 10.0.0.1 ifp vtnet0 mtu 1500 table inet.0 const char *cmd = get_action_name(hdr, RTM_NEWROUTE); - int len = print_line_prefix(cmd, "route"); + int len = print_line_prefix(hdr, cinfo, cmd, "route"); char buf[128]; print_prefix(h, buf, sizeof(buf), r.rta_dst, r.rtm_dst_len); @@ -547,7 +552,8 @@ }; static void -print_nlmsg_link(struct nl_helper *h, struct nlmsghdr *hdr) +print_nlmsg_link(struct nl_helper *h, struct nlmsghdr *hdr, + struct snl_msg_info *cinfo) { struct snl_parsed_link l = {}; struct snl_state *ss = &h->ss_cmd; @@ -557,7 +563,7 @@ // 20:19:41.333 add iface#3 vtnet0 admin UP oper UP mtu 1500 table inet.0 const char *cmd = get_action_name(hdr, RTM_NEWLINK); - print_line_prefix(cmd, "iface"); + print_line_prefix(hdr, cinfo, cmd, "iface"); printf("iface#%u %s ", l.ifi_index, l.ifla_ifname); printf("admin %s ", (l.ifi_flags & IFF_UP) ? "UP" : "DOWN"); @@ -570,7 +576,8 @@ } static void -print_nlmsg_addr(struct nl_helper *h, struct nlmsghdr *hdr) +print_nlmsg_addr(struct nl_helper *h, struct nlmsghdr *hdr, + struct snl_msg_info *cinfo) { struct snl_parsed_addr attrs = {}; struct snl_state *ss = &h->ss_cmd; @@ -580,7 +587,7 @@ // add addr 192.168.1.1/24 iface vtnet0 const char *cmd = get_action_name(hdr, RTM_NEWADDR); - print_line_prefix(cmd, "addr"); + print_line_prefix(hdr, cinfo, cmd, "addr"); char buf[128]; struct sockaddr *addr = attrs.ifa_local ? attrs.ifa_local : attrs.ifa_address; @@ -619,7 +626,8 @@ static void -print_nlmsg_neigh(struct nl_helper *h, struct nlmsghdr *hdr) +print_nlmsg_neigh(struct nl_helper *h, struct nlmsghdr *hdr, + struct snl_msg_info *cinfo) { struct snl_parsed_neigh attrs = {}; struct snl_state *ss = &h->ss_cmd; @@ -629,7 +637,7 @@ // add addr 192.168.1.1 state %s lladdr %s iface vtnet0 const char *cmd = get_action_name(hdr, RTM_NEWNEIGH); - print_line_prefix(cmd, "neigh"); + print_line_prefix(hdr, cinfo, cmd, "neigh"); char buf[128]; print_prefix(h, buf, sizeof(buf), attrs.nda_dst, -1); @@ -677,32 +685,35 @@ } static void -print_nlmsg_generic(struct nl_helper *h, struct nlmsghdr *hdr) +print_nlmsg_generic(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { + const char *cmd = get_action_name(hdr, 0); + print_line_prefix(hdr, cinfo, cmd, "unknown message"); + printf(" type %u\n", hdr->nlmsg_type); } static void -print_nlmsg(struct nl_helper *h, struct nlmsghdr *hdr) +print_nlmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { switch (hdr->nlmsg_type) { case RTM_NEWLINK: case RTM_DELLINK: - print_nlmsg_link(h, hdr); + print_nlmsg_link(h, hdr, cinfo); break; case RTM_NEWADDR: case RTM_DELADDR: - print_nlmsg_addr(h, hdr); + print_nlmsg_addr(h, hdr, cinfo); break; case RTM_NEWROUTE: case RTM_DELROUTE: - print_nlmsg_route(h, hdr); + print_nlmsg_route(h, hdr, cinfo); break; case RTM_NEWNEIGH: case RTM_DELNEIGH: - print_nlmsg_neigh(h, hdr); + print_nlmsg_neigh(h, hdr, cinfo); break; default: - print_nlmsg_generic(h, hdr); + print_nlmsg_generic(h, hdr, cinfo); } snl_clear_lb(&h->ss_cmd); @@ -731,6 +742,10 @@ #endif }; + int optval = 1; + socklen_t optlen = sizeof(optval); + setsockopt(ss_event.fd, SOL_NETLINK, NETLINK_MSG_INFO, &optval, optlen); + for (unsigned int i = 0; i < NL_ARRAY_LEN(groups); i++) { int error; int optval = groups[i]; @@ -741,11 +756,11 @@ warn("Unable to subscribe to group %d", optval); } + struct snl_msg_info attrs = {}; struct nlmsghdr *hdr; - while ((hdr = snl_read_message(&ss_event)) != NULL) + while ((hdr = snl_read_message_dbg(&ss_event, &attrs)) != NULL) { - // printf("-- MSG type %d--\n", hdr->nlmsg_type); - print_nlmsg(&h, hdr); + print_nlmsg(&h, hdr, &attrs); snl_clear_lb(&h.ss_cmd); snl_clear_lb(&ss_event); } @@ -798,8 +813,10 @@ return (true); }; - if (verbose) - print_nlmsg(h, hdr); + if (verbose) { + struct snl_msg_info attrs = {}; + print_nlmsg(h, hdr, &attrs); + } else { if (r->rta_multipath != NULL) { for (int i = 0; i < r->rta_multipath->num_nhops; i++) { @@ -847,8 +864,10 @@ if (!snl_parse_nlmsg(&ss, hdr, &snl_rtm_route_parser, &r)) continue; - if (verbose) - print_nlmsg(&h, hdr); + if (verbose) { + struct snl_msg_info attrs = {}; + print_nlmsg(&h, hdr, &attrs); + } if (r.rta_table != (uint32_t)fib || r.rtm_family != af) continue; if ((r.rta_rtflags & RTF_GATEWAY) == 0) diff --git a/sys/netlink/netlink.h b/sys/netlink/netlink.h --- a/sys/netlink/netlink.h +++ b/sys/netlink/netlink.h @@ -89,6 +89,7 @@ #define NETLINK_EXT_ACK 11 /* Ack support for receiving additional TLVs in ack */ #define NETLINK_GET_STRICT_CHK 12 /* Strict header checking */ +#define NETLINK_MSG_INFO 257 /* (FreeBSD-specific) Receive message originator data in cmsg */ /* * RFC 3549, 2.3.2 Netlink Message Header @@ -183,6 +184,15 @@ NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 }; +/* FreeBSD-specific debugging info */ + +enum nlmsginfo_attrs { + NLMSGINFO_ATTR_UNUSED, + NLMSGINFO_ATTR_PROCESS_ID = 1, /* u32, source process PID */ + NLMSGINFO_ATTR_PORT_ID = 2, /* u32, source socket nl_pid */ + NLMSGINFO_ATTR_SEQ_ID = 3, /* u32, source message seq_id */ +}; + #ifndef roundup2 #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ diff --git a/sys/netlink/netlink_ctl.h b/sys/netlink/netlink_ctl.h --- a/sys/netlink/netlink_ctl.h +++ b/sys/netlink/netlink_ctl.h @@ -108,5 +108,22 @@ typedef void (*genl_family_event_handler_t)(void *arg, const struct genl_family *gf, int action); EVENTHANDLER_DECLARE(genl_family_event, genl_family_event_handler_t); +struct thread; +#if defined(NETLINK) || defined(NETLINK_MODULE) +/* Provide optimized calls to the functions inside the same linking unit */ +struct nlpcb *_nl_get_thread_nlp(struct thread *td); + +static inline struct nlpcb * +nl_get_thread_nlp(struct thread *td) +{ + return (_nl_get_thread_nlp(td)); +} + +#else +/* Provide access to the functions via netlink_glue.c */ +struct nlpcb *nl_get_thread_nlp(struct thread *td); + +#endif + #endif #endif diff --git a/sys/netlink/netlink_domain.c b/sys/netlink/netlink_domain.c --- a/sys/netlink/netlink_domain.c +++ b/sys/netlink/netlink_domain.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,38 @@ sysctl_handle_nl_maxsockbuf, "LU", "Maximum Netlink socket buffer size"); + +static unsigned int osd_slot_id = 0; + +void +nl_osd_register(void) +{ + osd_slot_id = osd_register(OSD_THREAD, NULL, NULL); +} + +void +nl_osd_unregister(void) +{ + osd_deregister(OSD_THREAD, osd_slot_id); +} + +struct nlpcb * +_nl_get_thread_nlp(struct thread *td) +{ + return (osd_get(OSD_THREAD, &td->td_osd, osd_slot_id)); +} + +void +nl_set_thread_nlp(struct thread *td, struct nlpcb *nlp) +{ + NLP_LOG(LOG_DEBUG, nlp, "Set thread %p nlp to %p (slot %u)", td, nlp, osd_slot_id); + if (osd_set(OSD_THREAD, &td->td_osd, osd_slot_id, nlp) == 0) + return; + /* Failed, need to realloc */ + void **rsv = osd_reserve(osd_slot_id); + osd_set_reserved(OSD_THREAD, &td->td_osd, osd_slot_id, rsv, nlp); +} + /* * Looks up a nlpcb struct based on the @portid. Need to claim nlsock_mtx. * Returns nlpcb pointer if present else NULL @@ -144,6 +177,15 @@ return (groups_mask); } +static void +nl_send_one_group(struct mbuf *m, struct nlpcb *nlp, int num_messages, + int io_flags) +{ + if (__predict_false(nlp->nl_flags & NLF_MSG_INFO)) + nl_add_msg_info(m); + nl_send_one(m, nlp, num_messages, io_flags); +} + /* * Broadcasts message @m to the protocol @proto group specified by @group_id */ @@ -180,7 +222,8 @@ struct mbuf *m_copy; m_copy = m_copym(m, 0, M_COPYALL, M_NOWAIT); if (m_copy != NULL) - nl_send_one(m_copy, nlp_last, num_messages, io_flags); + nl_send_one_group(m_copy, nlp_last, + num_messages, io_flags); else { NLP_LOCK(nlp_last); if (nlp_last->nl_socket != NULL) @@ -192,7 +235,7 @@ } } if (nlp_last != NULL) - nl_send_one(m, nlp_last, num_messages, io_flags); + nl_send_one_group(m, nlp_last, num_messages, io_flags); else m_freem(m); @@ -296,6 +339,7 @@ nlp->nl_linux = is_linux; nlp->nl_active = true; nlp->nl_unconstrained_vnet = !jailed_without_vnet(so->so_cred); + nlp->nl_need_thread_setup = true; NLP_LOCK_INIT(nlp); refcount_init(&nlp->nl_refcount, 1); nl_init_io(nlp); @@ -589,6 +633,8 @@ return (NLF_EXT_ACK); case NETLINK_GET_STRICT_CHK: return (NLF_STRICT); + case NETLINK_MSG_INFO: + return (NLF_MSG_INFO); } return (0); @@ -630,19 +676,24 @@ case NETLINK_CAP_ACK: case NETLINK_EXT_ACK: case NETLINK_GET_STRICT_CHK: + case NETLINK_MSG_INFO: error = sooptcopyin(sopt, &optval, sizeof(optval), sizeof(optval)); if (error != 0) break; flag = nl_getoptflag(sopt->sopt_name); + if ((flag == NLF_MSG_INFO) && nlp->nl_linux) { + error = EINVAL; + break; + } + NLCTL_WLOCK(ctl); if (optval != 0) nlp->nl_flags |= flag; else nlp->nl_flags &= ~flag; NLCTL_WUNLOCK(ctl); - break; default: error = ENOPROTOOPT; } @@ -658,6 +709,7 @@ case NETLINK_CAP_ACK: case NETLINK_EXT_ACK: case NETLINK_GET_STRICT_CHK: + case NETLINK_MSG_INFO: NLCTL_RLOCK(ctl); optval = (nlp->nl_flags & nl_getoptflag(sopt->sopt_name)) != 0; NLCTL_RUNLOCK(ctl); diff --git a/sys/netlink/netlink_glue.c b/sys/netlink/netlink_glue.c --- a/sys/netlink/netlink_glue.c +++ b/sys/netlink/netlink_glue.c @@ -177,6 +177,12 @@ return (false); } +static struct nlpcb * +nl_get_thread_nlp_stub(struct thread *td __unused) +{ + return (NULL); +} + const static struct nl_function_wrapper nl_stub = { .nlmsg_add = nlmsg_add_stub, .nlmsg_refill_buffer = nlmsg_refill_buffer_stub, @@ -188,6 +194,7 @@ .nlmsg_get_group_writer = nlmsg_get_group_writer_stub, .nlmsg_get_chain_writer = nlmsg_get_chain_writer_stub, .nlmsg_end_dump = nlmsg_end_dump_stub, + .nl_get_thread_nlp = nl_get_thread_nlp_stub, }; /* @@ -262,5 +269,12 @@ { return (_nl->nlmsg_end_dump(nw, error, hdr)); } + +struct nlpcb * +nl_get_thread_nlp(struct thread *td) +{ + return (_nl->nl_get_thread_nlp(td)); +} + #endif /* !NETLINK */ diff --git a/sys/netlink/netlink_io.c b/sys/netlink/netlink_io.c --- a/sys/netlink/netlink_io.c +++ b/sys/netlink/netlink_io.c @@ -125,6 +125,55 @@ q->length = 0; } +void +nl_add_msg_info(struct mbuf *m) +{ + struct nlpcb *nlp = nl_get_thread_nlp(curthread); + NL_LOG(LOG_DEBUG2, "Trying to recover nlp from thread %p: %p", + curthread, nlp); + + if (nlp == NULL) + return; + + /* Prepare what we want to encode - PID, socket PID & msg seq */ + struct { + struct nlattr nla; + uint32_t val; + } data[] = { + { + .nla.nla_len = sizeof(struct nlattr) + sizeof(uint32_t), + .nla.nla_type = NLMSGINFO_ATTR_PROCESS_ID, + .val = nlp->nl_process_id, + }, + { + .nla.nla_len = sizeof(struct nlattr) + sizeof(uint32_t), + .nla.nla_type = NLMSGINFO_ATTR_PORT_ID, + .val = nlp->nl_port, + }, + }; + + + while (m->m_next != NULL) + m = m->m_next; + m->m_next = sbcreatecontrol(data, sizeof(data), + NETLINK_MSG_INFO, SOL_NETLINK, 0); + + NL_LOG(LOG_DEBUG2, "Storing %lu bytes of data, ctl: %p", sizeof(data), m->m_next); +} + +static __noinline struct mbuf * +extract_msg_info(struct mbuf *m) +{ + while (m->m_next != NULL) { + if (m->m_next->m_type == MT_CONTROL) { + struct mbuf *ctl = m->m_next; + m->m_next = NULL; + return (ctl); + } + m = m->m_next; + } + return (NULL); +} static void nl_schedule_taskqueue(struct nlpcb *nlp) @@ -181,10 +230,16 @@ while (true) { struct mbuf *m = queue_head(&nlp->tx_queue); - if (m && sbappendaddr_locked(sb, nl_empty_src, m, NULL) != 0) { - /* appended successfully */ - queue_pop(&nlp->tx_queue); - appended = true; + if (m != NULL) { + struct mbuf *ctl = NULL; + if (__predict_false(m->m_next != NULL)) + ctl = extract_msg_info(m); + if (sbappendaddr_locked(sb, nl_empty_src, m, ctl) != 0) { + /* appended successfully */ + queue_pop(&nlp->tx_queue); + appended = true; + } else + break; } else break; } @@ -257,6 +312,13 @@ { NL_LOG(LOG_DEBUG3, "taskqueue called"); + if (__predict_false(nlp->nl_need_thread_setup)) { + nl_set_thread_nlp(curthread, nlp); + NLP_LOCK(nlp); + nlp->nl_need_thread_setup = false; + NLP_UNLOCK(nlp); + } + while (nl_process_received_one(nlp)) ; } @@ -374,7 +436,10 @@ } struct socket *so = nlp->nl_socket; - if (sbappendaddr(&so->so_rcv, nl_empty_src, m, NULL) != 0) { + struct mbuf *ctl = NULL; + if (__predict_false(m->m_next != NULL)) + ctl = extract_msg_info(m); + if (sbappendaddr(&so->so_rcv, nl_empty_src, m, ctl) != 0) { sorwakeup(so); NLP_LOG(LOG_DEBUG3, nlp, "appended data & woken up"); } else { diff --git a/sys/netlink/netlink_module.c b/sys/netlink/netlink_module.c --- a/sys/netlink/netlink_module.c +++ b/sys/netlink/netlink_module.c @@ -219,6 +219,7 @@ switch (what) { case MOD_LOAD: NL_LOG(LOG_DEBUG2, "Loading"); + nl_osd_register(); #if !defined(NETLINK) && defined(NETLINK_MODULE) nl_set_functions(&nl_module); #endif @@ -232,6 +233,7 @@ #if !defined(NETLINK) && defined(NETLINK_MODULE) nl_set_functions(NULL); #endif + nl_osd_unregister(); } else ret = EBUSY; break; diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h --- a/sys/netlink/netlink_snl.h +++ b/sys/netlink/netlink_snl.h @@ -271,6 +271,55 @@ return (++ss->seq); } +struct snl_msg_info { + int cmsg_type; + int cmsg_level; + uint32_t process_id; + uint8_t port_id; + uint8_t seq_id; +}; +static inline bool parse_cmsg(struct snl_state *ss, const struct msghdr *msg, + struct snl_msg_info *attrs); + +static inline struct nlmsghdr * +snl_read_message_dbg(struct snl_state *ss, struct snl_msg_info *cinfo) +{ + memset(cinfo, 0, sizeof(*cinfo)); + + if (ss->off == ss->datalen) { + struct sockaddr_nl nladdr; + char cbuf[64]; + + struct iovec iov = { + .iov_base = ss->buf, + .iov_len = ss->bufsize, + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf), + }; + ss->off = 0; + ss->datalen = 0; + for (;;) { + ssize_t datalen = recvmsg(ss->fd, &msg, 0); + if (datalen > 0) { + ss->datalen = datalen; + parse_cmsg(ss, &msg, cinfo); + break; + } else if (errno != EINTR) + return (NULL); + } + } + struct nlmsghdr *hdr = (struct nlmsghdr *)(void *)&ss->buf[ss->off]; + ss->off += NLMSG_ALIGN(hdr->nlmsg_len); + return (hdr); +} + + static inline struct nlmsghdr * snl_read_message(struct snl_state *ss) { @@ -654,6 +703,33 @@ return (false); } +#define _OUT(_field) offsetof(struct snl_msg_info, _field) +static const struct snl_attr_parser _nla_p_cinfo[] = { + { .type = NLMSGINFO_ATTR_PROCESS_ID, .off = _OUT(process_id), .cb = snl_attr_get_uint32 }, + { .type = NLMSGINFO_ATTR_PORT_ID, .off = _OUT(port_id), .cb = snl_attr_get_uint32 }, + { .type = NLMSGINFO_ATTR_SEQ_ID, .off = _OUT(seq_id), .cb = snl_attr_get_uint32 }, +}; +#undef _OUT +SNL_DECLARE_ATTR_PARSER(snl_msg_info_parser, _nla_p_cinfo); + +static inline bool +parse_cmsg(struct snl_state *ss, const struct msghdr *msg, struct snl_msg_info *attrs) +{ + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level != SOL_NETLINK || cmsg->cmsg_type != NETLINK_MSG_INFO) + continue; + + void *data = CMSG_DATA(cmsg); + int len = cmsg->cmsg_len - ((char *)data - (char *)cmsg); + const struct snl_hdr_parser *ps = &snl_msg_info_parser; + + return (snl_parse_attrs_raw(ss, data, len, ps->np, ps->np_size, attrs)); + } + + return (false); +} + /* * Assumes e is zeroed */ diff --git a/sys/netlink/netlink_var.h b/sys/netlink/netlink_var.h --- a/sys/netlink/netlink_var.h +++ b/sys/netlink/netlink_var.h @@ -62,6 +62,7 @@ bool nl_tx_blocked; /* No new requests accepted */ bool nl_linux; /* true if running under compat */ bool nl_unconstrained_vnet; /* true if running under VNET jail (or without jail) */ + bool nl_need_thread_setup; struct nl_io_queue rx_queue; struct nl_io_queue tx_queue; struct taskqueue *nl_taskqueue; @@ -88,6 +89,7 @@ #define NLF_CAP_ACK 0x01 /* Do not send message body with errmsg */ #define NLF_EXT_ACK 0x02 /* Allow including extended TLVs in ack */ #define NLF_STRICT 0x04 /* Perform strict header checks */ +#define NLF_MSG_INFO 0x08 /* Send caller info along with the notifications */ SYSCTL_DECL(_net_netlink); SYSCTL_DECL(_net_netlink_debug); @@ -130,6 +132,9 @@ /* netlink_domain.c */ void nl_send_group(struct mbuf *m, int cnt, int proto, int group_id); +void nl_osd_register(void); +void nl_osd_unregister(void); +void nl_set_thread_nlp(struct thread *td, struct nlpcb *nlp); /* netlink_io.c */ #define NL_IOF_UNTRANSLATED 0x01 @@ -144,6 +149,8 @@ void nl_taskqueue_handler(void *_arg, int pending); int nl_receive_async(struct mbuf *m, struct socket *so); void nl_process_receive_locked(struct nlpcb *nlp); +void nl_set_source_metadata(struct mbuf *m, int num_messages); +void nl_add_msg_info(struct mbuf *m); /* netlink_generic.c */ struct genl_family { @@ -185,6 +192,7 @@ bool (*nlmsg_get_group_writer)(struct nl_writer *nw, int size, int protocol, int group_id); bool (*nlmsg_get_chain_writer)(struct nl_writer *nw, int size, struct mbuf **pm); bool (*nlmsg_end_dump)(struct nl_writer *nw, int error, struct nlmsghdr *hdr); + struct nlpcb * (*nl_get_thread_nlp)(struct thread *td); }; void nl_set_functions(const struct nl_function_wrapper *nl);