1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 Isilon Systems, LLC. 5 * Copyright (c) 2005-2014 Sandvine Incorporated. All rights reserved. 6 * Copyright (c) 2000 Darrell Anderson 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include "opt_inet.h" 35 36 #include <sys/param.h> 37 #include <sys/systm.h> 38 #include <sys/endian.h> 39 #include <sys/errno.h> 40 #include <sys/socket.h> 41 #include <sys/sysctl.h> 42 43 #include <net/ethernet.h> 44 #include <net/if.h> 45 #include <net/if_arp.h> 46 #include <net/if_dl.h> 47 #include <net/if_types.h> 48 #include <net/if_var.h> 49 50 #include <netinet/in.h> 51 #include <netinet/in_systm.h> 52 #include <netinet/in_var.h> 53 #include <netinet/ip.h> 54 #include <netinet/ip_var.h> 55 #include <netinet/ip_options.h> 56 #include <netinet/udp.h> 57 #include <netinet/udp_var.h> 58 59 #include <machine/in_cksum.h> 60 #include <machine/pcb.h> 61 62 #include <net/debugnet.h> 63 #define DEBUGNET_INTERNAL 64 #include <net/debugnet_int.h> 65 66 FEATURE(debugnet, "Debugnet support"); 67 68 SYSCTL_NODE(_net, OID_AUTO, debugnet, CTLFLAG_RD, NULL, 69 "debugnet parameters"); 70 71 unsigned debugnet_debug; 72 SYSCTL_UINT(_net_debugnet, OID_AUTO, debug, CTLFLAG_RWTUN, 73 &debugnet_debug, 0, 74 "Debug message verbosity (0: off; 1: on; 2: verbose)"); 75 76 int debugnet_npolls = 2000; 77 SYSCTL_INT(_net_debugnet, OID_AUTO, npolls, CTLFLAG_RWTUN, 78 &debugnet_npolls, 0, 79 "Number of times to poll before assuming packet loss (0.5ms per poll)"); 80 int debugnet_nretries = 10; 81 SYSCTL_INT(_net_debugnet, OID_AUTO, nretries, CTLFLAG_RWTUN, 82 &debugnet_nretries, 0, 83 "Number of retransmit attempts before giving up"); 84 85 static bool g_debugnet_pcb_inuse; 86 static struct debugnet_pcb g_dnet_pcb; 87 88 /* 89 * Simple accessors for opaque PCB. 90 */ 91 const unsigned char * 92 debugnet_get_gw_mac(const struct debugnet_pcb *pcb) 93 { 94 MPASS(g_debugnet_pcb_inuse && pcb == &g_dnet_pcb && 95 pcb->dp_state >= DN_STATE_HAVE_GW_MAC); 96 return (pcb->dp_gw_mac.octet); 97 } 98 99 /* 100 * Start of network primitives, beginning with output primitives. 101 */ 102 103 /* 104 * Handles creation of the ethernet header, then places outgoing packets into 105 * the tx buffer for the NIC 106 * 107 * Parameters: 108 * m The mbuf containing the packet to be sent (will be freed by 109 * this function or the NIC driver) 110 * ifp The interface to send on 111 * dst The destination ethernet address (source address will be looked 112 * up using ifp) 113 * etype The ETHERTYPE_* value for the protocol that is being sent 114 * 115 * Returns: 116 * int see errno.h, 0 for success 117 */ 118 int 119 debugnet_ether_output(struct mbuf *m, struct ifnet *ifp, struct ether_addr dst, 120 u_short etype) 121 { 122 struct ether_header *eh; 123 124 if (((ifp->if_flags & (IFF_MONITOR | IFF_UP)) != IFF_UP) || 125 (ifp->if_drv_flags & IFF_DRV_RUNNING) != IFF_DRV_RUNNING) { 126 if_printf(ifp, "%s: interface isn't up\n", __func__); 127 m_freem(m); 128 return (ENETDOWN); 129 } 130 131 /* Fill in the ethernet header. */ 132 M_PREPEND(m, ETHER_HDR_LEN, M_NOWAIT); 133 if (m == NULL) { 134 printf("%s: out of mbufs\n", __func__); 135 return (ENOBUFS); 136 } 137 eh = mtod(m, struct ether_header *); 138 memcpy(eh->ether_shost, IF_LLADDR(ifp), ETHER_ADDR_LEN); 139 memcpy(eh->ether_dhost, dst.octet, ETHER_ADDR_LEN); 140 eh->ether_type = htons(etype); 141 return (ifp->if_debugnet_methods->dn_transmit(ifp, m)); 142 } 143 144 /* 145 * Unreliable transmission of an mbuf chain to the debugnet server 146 * Note: can't handle fragmentation; fails if the packet is larger than 147 * ifp->if_mtu after adding the UDP/IP headers 148 * 149 * Parameters: 150 * pcb The debugnet context block 151 * m mbuf chain 152 * 153 * Returns: 154 * int see errno.h, 0 for success 155 */ 156 static int 157 debugnet_udp_output(struct debugnet_pcb *pcb, struct mbuf *m) 158 { 159 struct udphdr *udp; 160 161 MPASS(pcb->dp_state >= DN_STATE_HAVE_GW_MAC); 162 163 M_PREPEND(m, sizeof(*udp), M_NOWAIT); 164 if (m == NULL) { 165 printf("%s: out of mbufs\n", __func__); 166 return (ENOBUFS); 167 } 168 169 udp = mtod(m, void *); 170 udp->uh_ulen = htons(m->m_pkthdr.len); 171 /* Use this src port so that the server can connect() the socket */ 172 udp->uh_sport = htons(pcb->dp_client_ack_port); 173 udp->uh_dport = htons(pcb->dp_server_port); 174 /* Computed later (protocol-dependent). */ 175 udp->uh_sum = 0; 176 177 return (debugnet_ip_output(pcb, m)); 178 } 179 180 /* 181 * Dummy free function for debugnet clusters. 182 */ 183 static void 184 debugnet_mbuf_free(struct mbuf *m __unused) 185 { 186 } 187 188 /* 189 * Construct and reliably send a debugnet packet. May fail from a resource 190 * shortage or extreme number of unacknowledged retransmissions. Wait for 191 * an acknowledgement before returning. Splits packets into chunks small 192 * enough to be sent without fragmentation (looks up the interface MTU) 193 * 194 * Parameters: 195 * type debugnet packet type (HERALD, FINISHED, ...) 196 * data data 197 * datalen data size (bytes) 198 * auxdata optional auxiliary information 199 * 200 * Returns: 201 * int see errno.h, 0 for success 202 */ 203 int 204 debugnet_send(struct debugnet_pcb *pcb, uint32_t type, const void *data, 205 uint32_t datalen, const struct debugnet_proto_aux *auxdata) 206 { 207 struct debugnet_msg_hdr *dn_msg_hdr; 208 struct mbuf *m, *m2; 209 uint64_t want_acks; 210 uint32_t i, pktlen, sent_so_far; 211 int retries, polls, error; 212 213 want_acks = 0; 214 pcb->dp_rcvd_acks = 0; 215 retries = 0; 216 217 retransmit: 218 /* Chunks can be too big to fit in packets. */ 219 for (i = sent_so_far = 0; sent_so_far < datalen || 220 (i == 0 && datalen == 0); i++) { 221 pktlen = datalen - sent_so_far; 222 223 /* Bound: the interface MTU (assume no IP options). */ 224 pktlen = min(pktlen, pcb->dp_ifp->if_mtu - 225 sizeof(struct udpiphdr) - sizeof(struct debugnet_msg_hdr)); 226 227 /* 228 * Check if it is retransmitting and this has been ACKed 229 * already. 230 */ 231 if ((pcb->dp_rcvd_acks & (1 << i)) != 0) { 232 sent_so_far += pktlen; 233 continue; 234 } 235 236 /* 237 * Get and fill a header mbuf, then chain data as an extended 238 * mbuf. 239 */ 240 m = m_gethdr(M_NOWAIT, MT_DATA); 241 if (m == NULL) { 242 printf("%s: Out of mbufs\n", __func__); 243 return (ENOBUFS); 244 } 245 m->m_len = sizeof(struct debugnet_msg_hdr); 246 m->m_pkthdr.len = sizeof(struct debugnet_msg_hdr); 247 MH_ALIGN(m, sizeof(struct debugnet_msg_hdr)); 248 dn_msg_hdr = mtod(m, struct debugnet_msg_hdr *); 249 dn_msg_hdr->mh_seqno = htonl(pcb->dp_seqno + i); 250 dn_msg_hdr->mh_type = htonl(type); 251 dn_msg_hdr->mh_len = htonl(pktlen); 252 253 if (auxdata != NULL) { 254 dn_msg_hdr->mh_offset = 255 htobe64(auxdata->dp_offset_start + sent_so_far); 256 dn_msg_hdr->mh_aux2 = htobe32(auxdata->dp_aux2); 257 } else { 258 dn_msg_hdr->mh_offset = htobe64(sent_so_far); 259 dn_msg_hdr->mh_aux2 = 0; 260 } 261 262 if (pktlen != 0) { 263 m2 = m_get(M_NOWAIT, MT_DATA); 264 if (m2 == NULL) { 265 m_freem(m); 266 printf("%s: Out of mbufs\n", __func__); 267 return (ENOBUFS); 268 } 269 MEXTADD(m2, __DECONST(char *, data) + sent_so_far, 270 pktlen, debugnet_mbuf_free, NULL, NULL, 0, 271 EXT_DISPOSABLE); 272 m2->m_len = pktlen; 273 274 m_cat(m, m2); 275 m->m_pkthdr.len += pktlen; 276 } 277 error = debugnet_udp_output(pcb, m); 278 if (error != 0) 279 return (error); 280 281 /* Note that we're waiting for this packet in the bitfield. */ 282 want_acks |= (1 << i); 283 sent_so_far += pktlen; 284 } 285 if (i >= DEBUGNET_MAX_IN_FLIGHT) 286 printf("Warning: Sent more than %d packets (%d). " 287 "Acknowledgements will fail unless the size of " 288 "rcvd_acks/want_acks is increased.\n", 289 DEBUGNET_MAX_IN_FLIGHT, i); 290 291 /* 292 * Wait for acks. A *real* window would speed things up considerably. 293 */ 294 polls = 0; 295 while (pcb->dp_rcvd_acks != want_acks) { 296 if (polls++ > debugnet_npolls) { 297 if (retries++ > debugnet_nretries) 298 return (ETIMEDOUT); 299 printf(". "); 300 goto retransmit; 301 } 302 debugnet_network_poll(pcb->dp_ifp); 303 DELAY(500); 304 } 305 pcb->dp_seqno += i; 306 return (0); 307 } 308 309 /* 310 * Network input primitives. 311 */ 312 313 static void 314 debugnet_handle_ack(struct debugnet_pcb *pcb, struct mbuf **mb, uint16_t sport) 315 { 316 const struct debugnet_ack *dn_ack; 317 struct mbuf *m; 318 uint32_t rcv_ackno; 319 320 m = *mb; 321 322 if (m->m_pkthdr.len < sizeof(*dn_ack)) { 323 DNETDEBUG("ignoring small ACK packet\n"); 324 return; 325 } 326 /* Get Ack. */ 327 if (m->m_len < sizeof(*dn_ack)) { 328 m = m_pullup(m, sizeof(*dn_ack)); 329 *mb = m; 330 if (m == NULL) { 331 DNETDEBUG("m_pullup failed\n"); 332 return; 333 } 334 } 335 dn_ack = mtod(m, const void *); 336 337 /* Debugnet processing. */ 338 /* 339 * Packet is meant for us. Extract the ack sequence number and the 340 * port number if necessary. 341 */ 342 rcv_ackno = ntohl(dn_ack->da_seqno); 343 if (pcb->dp_state < DN_STATE_GOT_HERALD_PORT) { 344 pcb->dp_server_port = sport; 345 pcb->dp_state = DN_STATE_GOT_HERALD_PORT; 346 } 347 if (rcv_ackno >= pcb->dp_seqno + DEBUGNET_MAX_IN_FLIGHT) 348 printf("%s: ACK %u too far in future!\n", __func__, rcv_ackno); 349 else if (rcv_ackno >= pcb->dp_seqno) { 350 /* We're interested in this ack. Record it. */ 351 pcb->dp_rcvd_acks |= 1 << (rcv_ackno - pcb->dp_seqno); 352 } 353 } 354 355 void 356 debugnet_handle_udp(struct debugnet_pcb *pcb, struct mbuf **mb) 357 { 358 const struct udphdr *udp; 359 struct mbuf *m; 360 uint16_t sport; 361 362 /* UDP processing. */ 363 364 m = *mb; 365 if (m->m_pkthdr.len < sizeof(*udp)) { 366 DNETDEBUG("ignoring small UDP packet\n"); 367 return; 368 } 369 370 /* Get UDP headers. */ 371 if (m->m_len < sizeof(*udp)) { 372 m = m_pullup(m, sizeof(*udp)); 373 *mb = m; 374 if (m == NULL) { 375 DNETDEBUG("m_pullup failed\n"); 376 return; 377 } 378 } 379 udp = mtod(m, const void *); 380 381 /* For now, the only UDP packets we expect to receive are acks. */ 382 if (ntohs(udp->uh_dport) != pcb->dp_client_ack_port) { 383 DNETDEBUG("not on the expected ACK port.\n"); 384 return; 385 } 386 sport = ntohs(udp->uh_sport); 387 388 m_adj(m, sizeof(*udp)); 389 debugnet_handle_ack(pcb, mb, sport); 390 } 391 392 /* 393 * Handler for incoming packets directly from the network adapter 394 * Identifies the packet type (IP or ARP) and passes it along to one of the 395 * helper functions debugnet_handle_ip or debugnet_handle_arp. 396 * 397 * It needs to partially replicate the behaviour of ether_input() and 398 * ether_demux(). 399 * 400 * Parameters: 401 * ifp the interface the packet came from 402 * m an mbuf containing the packet received 403 */ 404 static void 405 debugnet_pkt_in(struct ifnet *ifp, struct mbuf *m) 406 { 407 struct ifreq ifr; 408 struct ether_header *eh; 409 u_short etype; 410 411 /* Ethernet processing. */ 412 if ((m->m_flags & M_PKTHDR) == 0) { 413 DNETDEBUG_IF(ifp, "discard frame without packet header\n"); 414 goto done; 415 } 416 if (m->m_len < ETHER_HDR_LEN) { 417 DNETDEBUG_IF(ifp, 418 "discard frame without leading eth header (len %u pktlen %u)\n", 419 m->m_len, m->m_pkthdr.len); 420 goto done; 421 } 422 if ((m->m_flags & M_HASFCS) != 0) { 423 m_adj(m, -ETHER_CRC_LEN); 424 m->m_flags &= ~M_HASFCS; 425 } 426 eh = mtod(m, struct ether_header *); 427 etype = ntohs(eh->ether_type); 428 if ((m->m_flags & M_VLANTAG) != 0 || etype == ETHERTYPE_VLAN) { 429 DNETDEBUG_IF(ifp, "ignoring vlan packets\n"); 430 goto done; 431 } 432 if (if_gethwaddr(ifp, &ifr) != 0) { 433 DNETDEBUG_IF(ifp, "failed to get hw addr for interface\n"); 434 goto done; 435 } 436 if (memcmp(ifr.ifr_addr.sa_data, eh->ether_dhost, 437 ETHER_ADDR_LEN) != 0 && 438 (etype != ETHERTYPE_ARP || !ETHER_IS_BROADCAST(eh->ether_dhost))) { 439 DNETDEBUG_IF(ifp, 440 "discard frame with incorrect destination addr\n"); 441 goto done; 442 } 443 444 MPASS(g_debugnet_pcb_inuse); 445 446 /* Done ethernet processing. Strip off the ethernet header. */ 447 m_adj(m, ETHER_HDR_LEN); 448 switch (etype) { 449 case ETHERTYPE_ARP: 450 debugnet_handle_arp(&g_dnet_pcb, &m); 451 break; 452 case ETHERTYPE_IP: 453 debugnet_handle_ip(&g_dnet_pcb, &m); 454 break; 455 default: 456 DNETDEBUG_IF(ifp, "dropping unknown ethertype %hu\n", etype); 457 break; 458 } 459 done: 460 if (m != NULL) 461 m_freem(m); 462 } 463 464 /* 465 * Network polling primitive. 466 * 467 * Instead of assuming that most of the network stack is sane, we just poll the 468 * driver directly for packets. 469 */ 470 void 471 debugnet_network_poll(struct ifnet *ifp) 472 { 473 ifp->if_debugnet_methods->dn_poll(ifp, 1000); 474 } 475 476 /* 477 * Start of consumer API surface. 478 */ 479 void 480 debugnet_free(struct debugnet_pcb *pcb) 481 { 482 struct ifnet *ifp; 483 484 MPASS(g_debugnet_pcb_inuse); 485 MPASS(pcb == &g_dnet_pcb); 486 487 ifp = pcb->dp_ifp; 488 ifp->if_input = pcb->dp_drv_input; 489 ifp->if_debugnet_methods->dn_event(ifp, DEBUGNET_END); 490 debugnet_mbuf_finish(); 491 492 g_debugnet_pcb_inuse = false; 493 memset(&g_dnet_pcb, 0xfd, sizeof(g_dnet_pcb)); 494 } 495 496 int 497 debugnet_connect(const struct debugnet_conn_params *dcp, 498 struct debugnet_pcb **pcb_out) 499 { 500 struct debugnet_pcb *pcb; 501 struct ifnet *ifp; 502 int error; 503 504 if (g_debugnet_pcb_inuse) { 505 printf("%s: Only one connection at a time.\n", __func__); 506 return (EBUSY); 507 } 508 509 pcb = &g_dnet_pcb; 510 *pcb = (struct debugnet_pcb) { 511 .dp_state = DN_STATE_INIT, 512 .dp_client = dcp->dc_client, 513 .dp_server = dcp->dc_server, 514 .dp_gateway = dcp->dc_gateway, 515 .dp_server_port = dcp->dc_herald_port, /* Initially */ 516 .dp_client_ack_port = dcp->dc_client_ack_port, 517 .dp_seqno = 1, 518 .dp_ifp = dcp->dc_ifp, 519 }; 520 521 /* Switch to the debugnet mbuf zones. */ 522 debugnet_mbuf_start(); 523 524 ifp = pcb->dp_ifp; 525 ifp->if_debugnet_methods->dn_event(ifp, DEBUGNET_START); 526 527 /* 528 * We maintain the invariant that g_debugnet_pcb_inuse is always true 529 * while the debugnet ifp's if_input is overridden with 530 * debugnet_pkt_in. 531 */ 532 g_debugnet_pcb_inuse = true; 533 534 /* Make the card use *our* receive callback. */ 535 pcb->dp_drv_input = ifp->if_input; 536 ifp->if_input = debugnet_pkt_in; 537 538 printf("%s: searching for %s MAC...\n", __func__, 539 (dcp->dc_gateway == INADDR_ANY) ? "server" : "gateway"); 540 541 error = debugnet_arp_gw(pcb); 542 if (error != 0) { 543 printf("%s: failed to locate MAC address\n", __func__); 544 goto cleanup; 545 } 546 MPASS(pcb->dp_state == DN_STATE_HAVE_GW_MAC); 547 548 error = debugnet_send(pcb, DEBUGNET_HERALD, dcp->dc_herald_data, 549 dcp->dc_herald_datalen, NULL); 550 if (error != 0) { 551 printf("%s: failed to herald debugnet server\n", __func__); 552 goto cleanup; 553 } 554 555 *pcb_out = pcb; 556 return (0); 557 558 cleanup: 559 debugnet_free(pcb); 560 return (error); 561 } 562 563 /* 564 * Pre-allocated dump-time mbuf tracking. 565 * 566 * We just track the high water mark we've ever seen and allocate appropriately 567 * for that iface/mtu combo. 568 */ 569 static struct { 570 int nmbuf; 571 int ncl; 572 int clsize; 573 } dn_hwm; 574 static struct mtx dn_hwm_lk; 575 MTX_SYSINIT(debugnet_hwm_lock, &dn_hwm_lk, "Debugnet HWM lock", MTX_DEF); 576 577 static void 578 dn_maybe_reinit_mbufs(int nmbuf, int ncl, int clsize) 579 { 580 bool any; 581 582 any = false; 583 mtx_lock(&dn_hwm_lk); 584 585 if (nmbuf > dn_hwm.nmbuf) { 586 any = true; 587 dn_hwm.nmbuf = nmbuf; 588 } else 589 nmbuf = dn_hwm.nmbuf; 590 591 if (ncl > dn_hwm.ncl) { 592 any = true; 593 dn_hwm.ncl = ncl; 594 } else 595 ncl = dn_hwm.ncl; 596 597 if (clsize > dn_hwm.clsize) { 598 any = true; 599 dn_hwm.clsize = clsize; 600 } else 601 clsize = dn_hwm.clsize; 602 603 mtx_unlock(&dn_hwm_lk); 604 605 if (any) 606 debugnet_mbuf_reinit(nmbuf, ncl, clsize); 607 } 608 609 void 610 debugnet_any_ifnet_update(struct ifnet *ifp) 611 { 612 int clsize, nmbuf, ncl, nrxr; 613 614 if (!DEBUGNET_SUPPORTED_NIC(ifp)) 615 return; 616 617 ifp->if_debugnet_methods->dn_init(ifp, &nrxr, &ncl, &clsize); 618 KASSERT(nrxr > 0, ("invalid receive ring count %d", nrxr)); 619 620 /* 621 * We need two headers per message on the transmit side. Multiply by 622 * four to give us some breathing room. 623 */ 624 nmbuf = ncl * (4 + nrxr); 625 ncl *= nrxr; 626 627 dn_maybe_reinit_mbufs(nmbuf, ncl, clsize); 628 } 629 630 /* 631 * Unfortunately, the ifnet_arrival_event eventhandler hook is mostly useless 632 * for us because drivers tend to if_attach before invoking DEBUGNET_SET(). 633 * 634 * On the other hand, hooking DEBUGNET_SET() itself may still be too early, 635 * because the driver is still in attach. Since we cannot use down interfaces, 636 * maybe hooking ifnet_event:IFNET_EVENT_UP is sufficient? ... Nope, at least 637 * with vtnet and dhcpclient that event just never occurs. 638 * 639 * So that's how I've landed on the lower level ifnet_link_event. 640 */ 641 642 static void 643 dn_ifnet_event(void *arg __unused, struct ifnet *ifp, int link_state) 644 { 645 if (link_state == LINK_STATE_UP) 646 debugnet_any_ifnet_update(ifp); 647 } 648 649 static eventhandler_tag dn_attach_cookie; 650 static void 651 dn_evh_init(void *ctx __unused) 652 { 653 dn_attach_cookie = EVENTHANDLER_REGISTER(ifnet_link_event, 654 dn_ifnet_event, NULL, EVENTHANDLER_PRI_ANY); 655 } 656 SYSINIT(dn_evh_init, SI_SUB_EVENTHANDLER + 1, SI_ORDER_ANY, dn_evh_init, NULL); 657