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 DNETDEBUG_IF(ifp, 439 "discard frame with incorrect destination addr\n"); 440 goto done; 441 } 442 443 MPASS(g_debugnet_pcb_inuse); 444 445 /* Done ethernet processing. Strip off the ethernet header. */ 446 m_adj(m, ETHER_HDR_LEN); 447 switch (etype) { 448 case ETHERTYPE_ARP: 449 debugnet_handle_arp(&g_dnet_pcb, &m); 450 break; 451 case ETHERTYPE_IP: 452 debugnet_handle_ip(&g_dnet_pcb, &m); 453 break; 454 default: 455 DNETDEBUG_IF(ifp, "dropping unknown ethertype %hu\n", etype); 456 break; 457 } 458 done: 459 if (m != NULL) 460 m_freem(m); 461 } 462 463 /* 464 * Network polling primitive. 465 * 466 * Instead of assuming that most of the network stack is sane, we just poll the 467 * driver directly for packets. 468 */ 469 void 470 debugnet_network_poll(struct ifnet *ifp) 471 { 472 ifp->if_debugnet_methods->dn_poll(ifp, 1000); 473 } 474 475 /* 476 * Start of consumer API surface. 477 */ 478 void 479 debugnet_free(struct debugnet_pcb *pcb) 480 { 481 struct ifnet *ifp; 482 483 MPASS(g_debugnet_pcb_inuse); 484 MPASS(pcb == &g_dnet_pcb); 485 486 ifp = pcb->dp_ifp; 487 ifp->if_input = pcb->dp_drv_input; 488 ifp->if_debugnet_methods->dn_event(ifp, DEBUGNET_END); 489 debugnet_mbuf_finish(); 490 491 g_debugnet_pcb_inuse = false; 492 memset(&g_dnet_pcb, 0xfd, sizeof(g_dnet_pcb)); 493 } 494 495 int 496 debugnet_connect(const struct debugnet_conn_params *dcp, 497 struct debugnet_pcb **pcb_out) 498 { 499 struct debugnet_pcb *pcb; 500 struct ifnet *ifp; 501 int error; 502 503 if (g_debugnet_pcb_inuse) { 504 printf("%s: Only one connection at a time.\n", __func__); 505 return (EBUSY); 506 } 507 508 pcb = &g_dnet_pcb; 509 *pcb = (struct debugnet_pcb) { 510 .dp_state = DN_STATE_INIT, 511 .dp_client = dcp->dc_client, 512 .dp_server = dcp->dc_server, 513 .dp_gateway = dcp->dc_gateway, 514 .dp_server_port = dcp->dc_herald_port, /* Initially */ 515 .dp_client_ack_port = dcp->dc_client_ack_port, 516 .dp_seqno = 1, 517 .dp_ifp = dcp->dc_ifp, 518 }; 519 520 /* Switch to the debugnet mbuf zones. */ 521 debugnet_mbuf_start(); 522 523 ifp = pcb->dp_ifp; 524 ifp->if_debugnet_methods->dn_event(ifp, DEBUGNET_START); 525 526 /* 527 * We maintain the invariant that g_debugnet_pcb_inuse is always true 528 * while the debugnet ifp's if_input is overridden with 529 * debugnet_pkt_in. 530 */ 531 g_debugnet_pcb_inuse = true; 532 533 /* Make the card use *our* receive callback. */ 534 pcb->dp_drv_input = ifp->if_input; 535 ifp->if_input = debugnet_pkt_in; 536 537 printf("%s: searching for %s MAC...\n", __func__, 538 (dcp->dc_gateway == INADDR_ANY) ? "server" : "gateway"); 539 540 error = debugnet_arp_gw(pcb); 541 if (error != 0) { 542 printf("%s: failed to locate MAC address\n", __func__); 543 goto cleanup; 544 } 545 MPASS(pcb->dp_state == DN_STATE_HAVE_GW_MAC); 546 547 error = debugnet_send(pcb, DEBUGNET_HERALD, dcp->dc_herald_data, 548 dcp->dc_herald_datalen, NULL); 549 if (error != 0) { 550 printf("%s: failed to herald debugnet server\n", __func__); 551 goto cleanup; 552 } 553 554 *pcb_out = pcb; 555 return (0); 556 557 cleanup: 558 debugnet_free(pcb); 559 return (error); 560 } 561 562 /* 563 * Pre-allocated dump-time mbuf tracking. 564 * 565 * We just track the high water mark we've ever seen and allocate appropriately 566 * for that iface/mtu combo. 567 */ 568 static struct { 569 int nmbuf; 570 int ncl; 571 int clsize; 572 } dn_hwm; 573 static struct mtx dn_hwm_lk; 574 MTX_SYSINIT(debugnet_hwm_lock, &dn_hwm_lk, "Debugnet HWM lock", MTX_DEF); 575 576 static void 577 dn_maybe_reinit_mbufs(int nmbuf, int ncl, int clsize) 578 { 579 bool any; 580 581 any = false; 582 mtx_lock(&dn_hwm_lk); 583 584 if (nmbuf > dn_hwm.nmbuf) { 585 any = true; 586 dn_hwm.nmbuf = nmbuf; 587 } else 588 nmbuf = dn_hwm.nmbuf; 589 590 if (ncl > dn_hwm.ncl) { 591 any = true; 592 dn_hwm.ncl = ncl; 593 } else 594 ncl = dn_hwm.ncl; 595 596 if (clsize > dn_hwm.clsize) { 597 any = true; 598 dn_hwm.clsize = clsize; 599 } else 600 clsize = dn_hwm.clsize; 601 602 mtx_unlock(&dn_hwm_lk); 603 604 if (any) 605 debugnet_mbuf_reinit(nmbuf, ncl, clsize); 606 } 607 608 void 609 debugnet_any_ifnet_update(struct ifnet *ifp) 610 { 611 int clsize, nmbuf, ncl, nrxr; 612 613 if (!DEBUGNET_SUPPORTED_NIC(ifp)) 614 return; 615 616 ifp->if_debugnet_methods->dn_init(ifp, &nrxr, &ncl, &clsize); 617 KASSERT(nrxr > 0, ("invalid receive ring count %d", nrxr)); 618 619 /* 620 * We need two headers per message on the transmit side. Multiply by 621 * four to give us some breathing room. 622 */ 623 nmbuf = ncl * (4 + nrxr); 624 ncl *= nrxr; 625 626 dn_maybe_reinit_mbufs(nmbuf, ncl, clsize); 627 } 628 629 /* 630 * Unfortunately, the ifnet_arrival_event eventhandler hook is mostly useless 631 * for us because drivers tend to if_attach before invoking DEBUGNET_SET(). 632 * 633 * On the other hand, hooking DEBUGNET_SET() itself may still be too early, 634 * because the driver is still in attach. Since we cannot use down interfaces, 635 * maybe hooking ifnet_event:IFNET_EVENT_UP is sufficient? ... Nope, at least 636 * with vtnet and dhcpclient that event just never occurs. 637 * 638 * So that's how I've landed on the lower level ifnet_link_event. 639 */ 640 641 static void 642 dn_ifnet_event(void *arg __unused, struct ifnet *ifp, int link_state) 643 { 644 if (link_state == LINK_STATE_UP) 645 debugnet_any_ifnet_update(ifp); 646 } 647 648 static eventhandler_tag dn_attach_cookie; 649 static void 650 dn_evh_init(void *ctx __unused) 651 { 652 dn_attach_cookie = EVENTHANDLER_REGISTER(ifnet_link_event, 653 dn_ifnet_event, NULL, EVENTHANDLER_PRI_ANY); 654 } 655 SYSINIT(dn_evh_init, SI_SUB_EVENTHANDLER + 1, SI_ORDER_ANY, dn_evh_init, NULL); 656