11db8d1f8SAna Kukec /*- 21db8d1f8SAna Kukec * Copyright (c) 2009-2010 Ana Kukec <anchie@FreeBSD.org> 31db8d1f8SAna Kukec * All rights reserved. 41db8d1f8SAna Kukec * 51db8d1f8SAna Kukec * Redistribution and use in source and binary forms, with or without 61db8d1f8SAna Kukec * modification, are permitted provided that the following conditions 71db8d1f8SAna Kukec * are met: 81db8d1f8SAna Kukec * 1. Redistributions of source code must retain the above copyright 91db8d1f8SAna Kukec * notice, this list of conditions and the following disclaimer. 101db8d1f8SAna Kukec * 2. Redistributions in binary form must reproduce the above copyright 111db8d1f8SAna Kukec * notice, this list of conditions and the following disclaimer in the 121db8d1f8SAna Kukec * documentation and/or other materials provided with the distribution. 131db8d1f8SAna Kukec * 141db8d1f8SAna Kukec * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 151db8d1f8SAna Kukec * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 161db8d1f8SAna Kukec * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 171db8d1f8SAna Kukec * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 181db8d1f8SAna Kukec * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 191db8d1f8SAna Kukec * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 201db8d1f8SAna Kukec * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 211db8d1f8SAna Kukec * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 221db8d1f8SAna Kukec * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 231db8d1f8SAna Kukec * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 241db8d1f8SAna Kukec * SUCH DAMAGE. 251db8d1f8SAna Kukec */ 261db8d1f8SAna Kukec 271db8d1f8SAna Kukec #include <sys/cdefs.h> 281db8d1f8SAna Kukec __FBSDID("$FreeBSD$"); 291db8d1f8SAna Kukec 30*57f60867SMark Johnston #include "opt_kdtrace.h" 31*57f60867SMark Johnston 321db8d1f8SAna Kukec #include <sys/param.h> 331db8d1f8SAna Kukec #include <sys/kernel.h> 341db8d1f8SAna Kukec #include <sys/mbuf.h> 351db8d1f8SAna Kukec #include <sys/module.h> 361db8d1f8SAna Kukec #include <sys/priv.h> 371db8d1f8SAna Kukec #include <sys/protosw.h> 38*57f60867SMark Johnston #include <sys/sdt.h> 391db8d1f8SAna Kukec #include <sys/systm.h> 401db8d1f8SAna Kukec #include <sys/socket.h> 411db8d1f8SAna Kukec #include <sys/sockstate.h> 421db8d1f8SAna Kukec #include <sys/sockbuf.h> 431db8d1f8SAna Kukec #include <sys/socketvar.h> 441db8d1f8SAna Kukec #include <sys/types.h> 451db8d1f8SAna Kukec 461db8d1f8SAna Kukec #include <net/route.h> 471db8d1f8SAna Kukec #include <net/if.h> 481db8d1f8SAna Kukec #include <net/if_var.h> 491db8d1f8SAna Kukec #include <net/vnet.h> 501db8d1f8SAna Kukec 511db8d1f8SAna Kukec #include <netinet/in.h> 52*57f60867SMark Johnston #include <netinet/in_kdtrace.h> 531db8d1f8SAna Kukec #include <netinet/ip_var.h> 541db8d1f8SAna Kukec #include <netinet/ip6.h> 551db8d1f8SAna Kukec #include <netinet/icmp6.h> 561db8d1f8SAna Kukec 571db8d1f8SAna Kukec #include <netinet6/in6_var.h> 581db8d1f8SAna Kukec #include <netinet6/nd6.h> 591db8d1f8SAna Kukec #include <netinet6/scope6_var.h> 601db8d1f8SAna Kukec #include <netinet6/send.h> 611db8d1f8SAna Kukec 62d745c852SEd Schouten static MALLOC_DEFINE(M_SEND, "send", "Secure Neighbour Discovery"); 631db8d1f8SAna Kukec 641db8d1f8SAna Kukec /* 651db8d1f8SAna Kukec * The socket used to communicate with the SeND daemon. 661db8d1f8SAna Kukec */ 673e288e62SDimitry Andric static VNET_DEFINE(struct socket *, send_so); 681db8d1f8SAna Kukec #define V_send_so VNET(send_so) 691db8d1f8SAna Kukec 701db8d1f8SAna Kukec u_long send_sendspace = 8 * (1024 + sizeof(struct sockaddr_send)); 711db8d1f8SAna Kukec u_long send_recvspace = 9216; 721db8d1f8SAna Kukec 731db8d1f8SAna Kukec struct mtx send_mtx; 741db8d1f8SAna Kukec #define SEND_LOCK_INIT() mtx_init(&send_mtx, "send_mtx", NULL, MTX_DEF) 751db8d1f8SAna Kukec #define SEND_LOCK() mtx_lock(&send_mtx) 761db8d1f8SAna Kukec #define SEND_UNLOCK() mtx_unlock(&send_mtx) 771db8d1f8SAna Kukec #define SEND_LOCK_DESTROY() mtx_destroy(&send_mtx) 781db8d1f8SAna Kukec 791db8d1f8SAna Kukec static int 801db8d1f8SAna Kukec send_attach(struct socket *so, int proto, struct thread *td) 811db8d1f8SAna Kukec { 821db8d1f8SAna Kukec int error; 831db8d1f8SAna Kukec 841db8d1f8SAna Kukec SEND_LOCK(); 851db8d1f8SAna Kukec if (V_send_so != NULL) { 861db8d1f8SAna Kukec SEND_UNLOCK(); 871db8d1f8SAna Kukec return (EEXIST); 881db8d1f8SAna Kukec } 891db8d1f8SAna Kukec 901db8d1f8SAna Kukec error = priv_check(td, PRIV_NETINET_RAW); 911db8d1f8SAna Kukec if (error) { 921db8d1f8SAna Kukec SEND_UNLOCK(); 931db8d1f8SAna Kukec return(error); 941db8d1f8SAna Kukec } 951db8d1f8SAna Kukec 961db8d1f8SAna Kukec if (proto != IPPROTO_SEND) { 971db8d1f8SAna Kukec SEND_UNLOCK(); 981db8d1f8SAna Kukec return (EPROTONOSUPPORT); 991db8d1f8SAna Kukec } 1001db8d1f8SAna Kukec error = soreserve(so, send_sendspace, send_recvspace); 1011db8d1f8SAna Kukec if (error) { 1021db8d1f8SAna Kukec SEND_UNLOCK(); 1031db8d1f8SAna Kukec return(error); 1041db8d1f8SAna Kukec } 1051db8d1f8SAna Kukec 1061db8d1f8SAna Kukec V_send_so = so; 1071db8d1f8SAna Kukec SEND_UNLOCK(); 1081db8d1f8SAna Kukec 1091db8d1f8SAna Kukec return (0); 1101db8d1f8SAna Kukec } 1111db8d1f8SAna Kukec 1121db8d1f8SAna Kukec static int 1131db8d1f8SAna Kukec send_output(struct mbuf *m, struct ifnet *ifp, int direction) 1141db8d1f8SAna Kukec { 1151db8d1f8SAna Kukec struct ip6_hdr *ip6; 1161db8d1f8SAna Kukec struct sockaddr_in6 dst; 1171db8d1f8SAna Kukec struct icmp6_hdr *icmp6; 1181db8d1f8SAna Kukec int icmp6len; 1191db8d1f8SAna Kukec 1201db8d1f8SAna Kukec /* 1211db8d1f8SAna Kukec * Receive incoming (SeND-protected) or outgoing traffic 1221db8d1f8SAna Kukec * (SeND-validated) from the SeND user space application. 1231db8d1f8SAna Kukec */ 1241db8d1f8SAna Kukec 1251db8d1f8SAna Kukec switch (direction) { 1261db8d1f8SAna Kukec case SND_IN: 1271db8d1f8SAna Kukec if (m->m_len < (sizeof(struct ip6_hdr) + 1281db8d1f8SAna Kukec sizeof(struct icmp6_hdr))) { 1291db8d1f8SAna Kukec m = m_pullup(m, sizeof(struct ip6_hdr) + 1301db8d1f8SAna Kukec sizeof(struct icmp6_hdr)); 1311db8d1f8SAna Kukec if (!m) 1321db8d1f8SAna Kukec return (ENOBUFS); 1331db8d1f8SAna Kukec } 1341db8d1f8SAna Kukec 1351db8d1f8SAna Kukec /* Before passing off the mbuf record the proper interface. */ 1361db8d1f8SAna Kukec m->m_pkthdr.rcvif = ifp; 1371db8d1f8SAna Kukec 1381db8d1f8SAna Kukec if (m->m_flags & M_PKTHDR) 1391db8d1f8SAna Kukec icmp6len = m->m_pkthdr.len - sizeof(struct ip6_hdr); 1401db8d1f8SAna Kukec else 1411db8d1f8SAna Kukec panic("Doh! not the first mbuf."); 1421db8d1f8SAna Kukec 1431db8d1f8SAna Kukec ip6 = mtod(m, struct ip6_hdr *); 1441db8d1f8SAna Kukec icmp6 = (struct icmp6_hdr *)(ip6 + 1); 1451db8d1f8SAna Kukec 1461db8d1f8SAna Kukec /* 1471db8d1f8SAna Kukec * Output the packet as icmp6.c:icpm6_input() would do. 1481db8d1f8SAna Kukec * The mbuf is always consumed, so we do not have to 1491db8d1f8SAna Kukec * care about that. 1501db8d1f8SAna Kukec */ 1511db8d1f8SAna Kukec switch (icmp6->icmp6_type) { 1521db8d1f8SAna Kukec case ND_NEIGHBOR_SOLICIT: 1531db8d1f8SAna Kukec nd6_ns_input(m, sizeof(struct ip6_hdr), icmp6len); 1541db8d1f8SAna Kukec break; 1551db8d1f8SAna Kukec case ND_NEIGHBOR_ADVERT: 1561db8d1f8SAna Kukec nd6_na_input(m, sizeof(struct ip6_hdr), icmp6len); 1571db8d1f8SAna Kukec break; 1581db8d1f8SAna Kukec case ND_REDIRECT: 1591db8d1f8SAna Kukec icmp6_redirect_input(m, sizeof(struct ip6_hdr)); 1601db8d1f8SAna Kukec break; 1611db8d1f8SAna Kukec case ND_ROUTER_SOLICIT: 1621db8d1f8SAna Kukec nd6_rs_input(m, sizeof(struct ip6_hdr), icmp6len); 1631db8d1f8SAna Kukec break; 1641db8d1f8SAna Kukec case ND_ROUTER_ADVERT: 1651db8d1f8SAna Kukec nd6_ra_input(m, sizeof(struct ip6_hdr), icmp6len); 1661db8d1f8SAna Kukec break; 1671db8d1f8SAna Kukec default: 1681db8d1f8SAna Kukec return (ENOSYS); 1691db8d1f8SAna Kukec } 1701db8d1f8SAna Kukec return (0); 1711db8d1f8SAna Kukec 1721db8d1f8SAna Kukec case SND_OUT: 1731db8d1f8SAna Kukec if (m->m_len < sizeof(struct ip6_hdr)) { 1741db8d1f8SAna Kukec m = m_pullup(m, sizeof(struct ip6_hdr)); 1751db8d1f8SAna Kukec if (!m) 1761db8d1f8SAna Kukec return (ENOBUFS); 1771db8d1f8SAna Kukec } 1781db8d1f8SAna Kukec ip6 = mtod(m, struct ip6_hdr *); 1791db8d1f8SAna Kukec if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) 1801db8d1f8SAna Kukec m->m_flags |= M_MCAST; 1811db8d1f8SAna Kukec 1821db8d1f8SAna Kukec bzero(&dst, sizeof(dst)); 1831db8d1f8SAna Kukec dst.sin6_family = AF_INET6; 1841db8d1f8SAna Kukec dst.sin6_len = sizeof(dst); 1851db8d1f8SAna Kukec dst.sin6_addr = ip6->ip6_dst; 1861db8d1f8SAna Kukec 18786bd0491SAndre Oppermann m_clrprotoflags(m); /* Avoid confusing lower layers. */ 18886bd0491SAndre Oppermann 189*57f60867SMark Johnston IP_PROBE(send, NULL, NULL, ip6, ifp, NULL, ip6); 190*57f60867SMark Johnston 1911db8d1f8SAna Kukec /* 1921db8d1f8SAna Kukec * Output the packet as nd6.c:nd6_output_lle() would do. 1931db8d1f8SAna Kukec * The mbuf is always consumed, so we do not have to care 1941db8d1f8SAna Kukec * about that. 1951db8d1f8SAna Kukec * XXX-BZ as we added data, what about fragmenting, 1961db8d1f8SAna Kukec * if now needed? 1971db8d1f8SAna Kukec */ 1981db8d1f8SAna Kukec int error; 1991db8d1f8SAna Kukec error = ((*ifp->if_output)(ifp, m, (struct sockaddr *)&dst, 2001db8d1f8SAna Kukec NULL)); 2011db8d1f8SAna Kukec if (error) 2021db8d1f8SAna Kukec error = ENOENT; 2031db8d1f8SAna Kukec return (error); 2041db8d1f8SAna Kukec 2051db8d1f8SAna Kukec default: 2061db8d1f8SAna Kukec panic("%s: direction %d neither SND_IN nor SND_OUT.", 2071db8d1f8SAna Kukec __func__, direction); 2081db8d1f8SAna Kukec } 2091db8d1f8SAna Kukec } 2101db8d1f8SAna Kukec 2111db8d1f8SAna Kukec /* 2121db8d1f8SAna Kukec * Receive a SeND message from user space to be either send out by the kernel 2131db8d1f8SAna Kukec * or, with SeND ICMPv6 options removed, to be further processed by the icmp6 2141db8d1f8SAna Kukec * input path. 2151db8d1f8SAna Kukec */ 2161db8d1f8SAna Kukec static int 2171db8d1f8SAna Kukec send_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, 2181db8d1f8SAna Kukec struct mbuf *control, struct thread *td) 2191db8d1f8SAna Kukec { 2201db8d1f8SAna Kukec struct sockaddr_send *sendsrc; 2211db8d1f8SAna Kukec struct ifnet *ifp; 2221db8d1f8SAna Kukec int error; 2231db8d1f8SAna Kukec 2241db8d1f8SAna Kukec KASSERT(V_send_so == so, ("%s: socket %p not send socket %p", 2251db8d1f8SAna Kukec __func__, so, V_send_so)); 2261db8d1f8SAna Kukec 2271db8d1f8SAna Kukec sendsrc = (struct sockaddr_send *)nam; 2281db8d1f8SAna Kukec ifp = ifnet_byindex_ref(sendsrc->send_ifidx); 2291db8d1f8SAna Kukec if (ifp == NULL) { 2301db8d1f8SAna Kukec error = ENETUNREACH; 2311db8d1f8SAna Kukec goto err; 2321db8d1f8SAna Kukec } 2331db8d1f8SAna Kukec 2341db8d1f8SAna Kukec error = send_output(m, ifp, sendsrc->send_direction); 2351db8d1f8SAna Kukec if_rele(ifp); 2361db8d1f8SAna Kukec m = NULL; 2371db8d1f8SAna Kukec 2381db8d1f8SAna Kukec err: 2391db8d1f8SAna Kukec if (m != NULL) 2401db8d1f8SAna Kukec m_freem(m); 2411db8d1f8SAna Kukec return (error); 2421db8d1f8SAna Kukec } 2431db8d1f8SAna Kukec 2441db8d1f8SAna Kukec static void 2451db8d1f8SAna Kukec send_close(struct socket *so) 2461db8d1f8SAna Kukec { 2471db8d1f8SAna Kukec 2481db8d1f8SAna Kukec SEND_LOCK(); 2491db8d1f8SAna Kukec if (V_send_so) 2501db8d1f8SAna Kukec V_send_so = NULL; 2511db8d1f8SAna Kukec SEND_UNLOCK(); 2521db8d1f8SAna Kukec } 2531db8d1f8SAna Kukec 2541db8d1f8SAna Kukec /* 2551db8d1f8SAna Kukec * Send a SeND message to user space, that was either received and has to be 2561db8d1f8SAna Kukec * validated or was about to be send out and has to be handled by the SEND 2571db8d1f8SAna Kukec * daemon adding SeND ICMPv6 options. 2581db8d1f8SAna Kukec */ 2591db8d1f8SAna Kukec static int 2601db8d1f8SAna Kukec send_input(struct mbuf *m, struct ifnet *ifp, int direction, int msglen __unused) 2611db8d1f8SAna Kukec { 2621db8d1f8SAna Kukec struct ip6_hdr *ip6; 2631db8d1f8SAna Kukec struct sockaddr_send sendsrc; 2641db8d1f8SAna Kukec 2651db8d1f8SAna Kukec SEND_LOCK(); 2661db8d1f8SAna Kukec if (V_send_so == NULL) { 2671db8d1f8SAna Kukec SEND_UNLOCK(); 2681db8d1f8SAna Kukec return (-1); 2691db8d1f8SAna Kukec } 2701db8d1f8SAna Kukec 2711db8d1f8SAna Kukec /* 2721db8d1f8SAna Kukec * Make sure to clear any possible internally embedded scope before 2731db8d1f8SAna Kukec * passing the packet to user space for SeND cryptographic signature 2741db8d1f8SAna Kukec * validation to succeed. 2751db8d1f8SAna Kukec */ 2761db8d1f8SAna Kukec ip6 = mtod(m, struct ip6_hdr *); 2771db8d1f8SAna Kukec in6_clearscope(&ip6->ip6_src); 2781db8d1f8SAna Kukec in6_clearscope(&ip6->ip6_dst); 2791db8d1f8SAna Kukec 2801db8d1f8SAna Kukec bzero(&sendsrc, sizeof(sendsrc)); 2811db8d1f8SAna Kukec sendsrc.send_len = sizeof(sendsrc); 2821db8d1f8SAna Kukec sendsrc.send_family = AF_INET6; 2831db8d1f8SAna Kukec sendsrc.send_direction = direction; 2841db8d1f8SAna Kukec sendsrc.send_ifidx = ifp->if_index; 2851db8d1f8SAna Kukec 2861db8d1f8SAna Kukec /* 2871db8d1f8SAna Kukec * Send incoming or outgoing traffic to user space either to be 2881db8d1f8SAna Kukec * protected (outgoing) or validated (incoming) according to rfc3971. 2891db8d1f8SAna Kukec */ 2901db8d1f8SAna Kukec SOCKBUF_LOCK(&V_send_so->so_rcv); 2911db8d1f8SAna Kukec if (sbappendaddr_locked(&V_send_so->so_rcv, 2921db8d1f8SAna Kukec (struct sockaddr *)&sendsrc, m, NULL) == 0) { 2931db8d1f8SAna Kukec SOCKBUF_UNLOCK(&V_send_so->so_rcv); 2941db8d1f8SAna Kukec /* XXX stats. */ 2951db8d1f8SAna Kukec m_freem(m); 2961db8d1f8SAna Kukec } else { 2971db8d1f8SAna Kukec sorwakeup_locked(V_send_so); 2981db8d1f8SAna Kukec } 2991db8d1f8SAna Kukec 3001db8d1f8SAna Kukec SEND_UNLOCK(); 3011db8d1f8SAna Kukec return (0); 3021db8d1f8SAna Kukec } 3031db8d1f8SAna Kukec 3041db8d1f8SAna Kukec struct pr_usrreqs send_usrreqs = { 3051db8d1f8SAna Kukec .pru_attach = send_attach, 3061db8d1f8SAna Kukec .pru_send = send_send, 3071db8d1f8SAna Kukec .pru_detach = send_close 3081db8d1f8SAna Kukec }; 3091db8d1f8SAna Kukec struct protosw send_protosw = { 3101db8d1f8SAna Kukec .pr_type = SOCK_RAW, 3111db8d1f8SAna Kukec .pr_flags = PR_ATOMIC|PR_ADDR, 3121db8d1f8SAna Kukec .pr_protocol = IPPROTO_SEND, 3131db8d1f8SAna Kukec .pr_usrreqs = &send_usrreqs 3141db8d1f8SAna Kukec }; 3151db8d1f8SAna Kukec 3161db8d1f8SAna Kukec static int 3171db8d1f8SAna Kukec send_modevent(module_t mod, int type, void *unused) 3181db8d1f8SAna Kukec { 3191db8d1f8SAna Kukec #ifdef __notyet__ 3201db8d1f8SAna Kukec VNET_ITERATOR_DECL(vnet_iter); 3211db8d1f8SAna Kukec #endif 3221db8d1f8SAna Kukec int error; 3231db8d1f8SAna Kukec 3241db8d1f8SAna Kukec switch (type) { 3251db8d1f8SAna Kukec case MOD_LOAD: 3261db8d1f8SAna Kukec SEND_LOCK_INIT(); 3271db8d1f8SAna Kukec 3281db8d1f8SAna Kukec error = pf_proto_register(PF_INET6, &send_protosw); 3291db8d1f8SAna Kukec if (error != 0) { 3301db8d1f8SAna Kukec printf("%s:%d: MOD_LOAD pf_proto_register(): %d\n", 3311db8d1f8SAna Kukec __func__, __LINE__, error); 3321db8d1f8SAna Kukec SEND_LOCK_DESTROY(); 3331db8d1f8SAna Kukec break; 3341db8d1f8SAna Kukec } 3351db8d1f8SAna Kukec send_sendso_input_hook = send_input; 3361db8d1f8SAna Kukec break; 3371db8d1f8SAna Kukec case MOD_UNLOAD: 3381db8d1f8SAna Kukec /* Do not allow unloading w/o locking. */ 3391db8d1f8SAna Kukec return (EBUSY); 3401db8d1f8SAna Kukec #ifdef __notyet__ 3411db8d1f8SAna Kukec VNET_LIST_RLOCK_NOSLEEP(); 3421db8d1f8SAna Kukec SEND_LOCK(); 3431db8d1f8SAna Kukec VNET_FOREACH(vnet_iter) { 3441db8d1f8SAna Kukec CURVNET_SET(vnet_iter); 3451db8d1f8SAna Kukec if (V_send_so != NULL) { 3461db8d1f8SAna Kukec CURVNET_RESTORE(); 3471db8d1f8SAna Kukec SEND_UNLOCK(); 3481db8d1f8SAna Kukec VNET_LIST_RUNLOCK_NOSLEEP(); 3491db8d1f8SAna Kukec return (EBUSY); 3501db8d1f8SAna Kukec } 3511db8d1f8SAna Kukec CURVNET_RESTORE(); 3521db8d1f8SAna Kukec } 3531db8d1f8SAna Kukec SEND_UNLOCK(); 3541db8d1f8SAna Kukec VNET_LIST_RUNLOCK_NOSLEEP(); 3551db8d1f8SAna Kukec error = pf_proto_unregister(PF_INET6, IPPROTO_SEND, SOCK_RAW); 3561db8d1f8SAna Kukec if (error == 0) 3571db8d1f8SAna Kukec SEND_LOCK_DESTROY(); 3581db8d1f8SAna Kukec send_sendso_input_hook = NULL; 3591db8d1f8SAna Kukec break; 3601db8d1f8SAna Kukec #endif 3611db8d1f8SAna Kukec default: 3621db8d1f8SAna Kukec error = 0; 3631db8d1f8SAna Kukec break; 3641db8d1f8SAna Kukec } 3651db8d1f8SAna Kukec 3661db8d1f8SAna Kukec return (error); 3671db8d1f8SAna Kukec } 3681db8d1f8SAna Kukec 3691db8d1f8SAna Kukec static moduledata_t sendmod = { 3701db8d1f8SAna Kukec "send", 3711db8d1f8SAna Kukec send_modevent, 3729823d527SKevin Lo 0 3731db8d1f8SAna Kukec }; 3741db8d1f8SAna Kukec 3751db8d1f8SAna Kukec DECLARE_MODULE(send, sendmod, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY); 376