2 This file is part of GNUnet.
3 Copyright (C) 2007-2016 GNUnet e.V.
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
22 * @author Christian Grothoff
23 * @author Milan Bouchet-Valat
26 * Service for handling UPnP and NAT-PMP port forwarding
27 * and external IP address retrieval
30 #include "gnunet_nat_service.h"
36 * Entry in DLL of addresses of this peer.
44 struct AddrEntry *next;
49 struct AddrEntry *prev;
52 * Address class of the address.
54 enum GNUNET_NAT_AddressClass ac;
57 * Number of bytes that follow.
64 * Handle for active NAT registrations.
66 struct GNUNET_NAT_Handle
70 * Configuration we use.
72 const struct GNUNET_CONFIGURATION_Handle *cfg;
75 * Message queue for communicating with the NAT service.
77 struct GNUNET_MQ_Handle *mq;
80 * Our registration message.
82 struct GNUNET_MessageHeader *reg;
85 * Head of address DLL.
87 struct AddrEntry *ae_head;
90 * Tail of address DLL.
92 struct AddrEntry *ae_tail;
95 * Function to call when our addresses change.
97 GNUNET_NAT_AddressCallback address_callback;
100 * Function to call when another peer requests connection reversal.
102 GNUNET_NAT_ReversalCallback reversal_callback;
105 * Closure for the various callbacks.
110 * Task scheduled to reconnect to the service.
112 struct GNUNET_SCHEDULER_Task *reconnect_task;
115 * How long to wait until we reconnect.
117 struct GNUNET_TIME_Relative reconnect_delay;
122 * Task to connect to the NAT service.
124 * @param cls our `struct GNUNET_NAT_Handle *`
127 do_connect (void *cls);
131 * Task to connect to the NAT service.
133 * @param nh handle to reconnect
136 reconnect (struct GNUNET_NAT_Handle *nh)
138 struct AddrEntry *ae;
142 GNUNET_MQ_destroy (nh->mq);
145 while (NULL != (ae = nh->ae_head))
147 GNUNET_CONTAINER_DLL_remove (nh->ae_head,
150 nh->address_callback (nh->callback_cls,
153 (const struct sockaddr *) &ae[1],
158 = GNUNET_TIME_STD_BACKOFF (nh->reconnect_delay);
160 = GNUNET_SCHEDULER_add_delayed (nh->reconnect_delay,
167 * Check connection reversal request.
169 * @param cls our `struct GNUNET_NAT_Handle`
170 * @param crm the message
171 * @return #GNUNET_OK if @a crm is well-formed
174 check_connection_reversal_request (void *cls,
175 const struct GNUNET_NAT_ConnectionReversalRequestedMessage *crm)
177 if (ntohs (crm->header.size) !=
179 ntohs (crm->local_addr_size) +
180 ntohs (crm->remote_addr_size) )
183 return GNUNET_SYSERR;
185 if ( (sizeof (struct sockaddr_in) != ntohs (crm->local_addr_size)) ||
186 (sizeof (struct sockaddr_in) != ntohs (crm->remote_addr_size)) )
189 return GNUNET_SYSERR;
196 * Handle connection reversal request.
198 * @param cls our `struct GNUNET_NAT_Handle`
199 * @param crm the message
202 handle_connection_reversal_request (void *cls,
203 const struct GNUNET_NAT_ConnectionReversalRequestedMessage *crm)
205 struct GNUNET_NAT_Handle *nh = cls;
206 const struct sockaddr_in *local_sa = (const struct sockaddr_in *) &crm[1];
207 const struct sockaddr_in *remote_sa = &local_sa[1];
209 nh->reversal_callback (nh->callback_cls,
210 (const struct sockaddr *) local_sa,
211 sizeof (struct sockaddr_in),
212 (const struct sockaddr *) remote_sa,
213 sizeof (struct sockaddr_in));
218 * Check address change notification.
220 * @param cls our `struct GNUNET_NAT_Handle`
221 * @param acn the message
222 * @return #GNUNET_OK if @a crm is well-formed
225 check_address_change_notification (void *cls,
226 const struct GNUNET_NAT_AddressChangeNotificationMessage *acn)
228 size_t alen = ntohs (acn->header.size) - sizeof (*acn);
232 case sizeof (struct sockaddr_in):
234 const struct sockaddr_in *s4
235 = (const struct sockaddr_in *) &acn[1];
236 if (AF_INET != s4->sin_family)
239 return GNUNET_SYSERR;
243 case sizeof (struct sockaddr_in6):
245 const struct sockaddr_in6 *s6
246 = (const struct sockaddr_in6 *) &acn[1];
247 if (AF_INET6 != s6->sin6_family)
250 return GNUNET_SYSERR;
256 return GNUNET_SYSERR;
263 * Handle connection reversal request.
265 * @param cls our `struct GNUNET_NAT_Handle`
266 * @param acn the message
269 handle_address_change_notification (void *cls,
270 const struct GNUNET_NAT_AddressChangeNotificationMessage *acn)
272 struct GNUNET_NAT_Handle *nh = cls;
273 size_t alen = ntohs (acn->header.size) - sizeof (*acn);
274 const struct sockaddr *sa = (const struct sockaddr *) &acn[1];
275 enum GNUNET_NAT_AddressClass ac;
276 struct AddrEntry *ae;
278 ac = (enum GNUNET_NAT_AddressClass) ntohl (acn->addr_class);
279 if (GNUNET_YES == ntohl (acn->add_remove))
281 ae = GNUNET_malloc (sizeof (*ae) + alen);
284 GNUNET_memcpy (&ae[1],
287 GNUNET_CONTAINER_DLL_insert (nh->ae_head,
293 for (ae = nh->ae_head; NULL != ae; ae = ae->next)
294 if ( (ae->addrlen == alen) &&
295 (0 == memcmp (&ae[1],
305 GNUNET_CONTAINER_DLL_remove (nh->ae_head,
310 nh->address_callback (nh->callback_cls,
311 ntohl (acn->add_remove),
319 * Handle queue errors by reconnecting to NAT.
321 * @param cls the `struct GNUNET_NAT_Handle *`
322 * @param error details about the error
325 mq_error_handler (void *cls,
326 enum GNUNET_MQ_Error error)
328 struct GNUNET_NAT_Handle *nh = cls;
335 * Task to connect to the NAT service.
337 * @param cls our `struct GNUNET_NAT_Handle *`
340 do_connect (void *cls)
342 struct GNUNET_NAT_Handle *nh = cls;
343 struct GNUNET_MQ_MessageHandler handlers[] = {
344 GNUNET_MQ_hd_var_size (connection_reversal_request,
345 GNUNET_MESSAGE_TYPE_NAT_CONNECTION_REVERSAL_REQUESTED,
346 struct GNUNET_NAT_ConnectionReversalRequestedMessage,
348 GNUNET_MQ_hd_var_size (address_change_notification,
349 GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE,
350 struct GNUNET_NAT_AddressChangeNotificationMessage,
352 GNUNET_MQ_handler_end ()
354 struct GNUNET_MQ_Envelope *env;
356 nh->reconnect_task = NULL;
357 nh->mq = GNUNET_CLIENT_connecT (nh->cfg,
367 env = GNUNET_MQ_msg_copy (nh->reg);
368 GNUNET_MQ_send (nh->mq,
374 * Attempt to enable port redirection and detect public IP address
375 * contacting UPnP or NAT-PMP routers on the local network. Use @a
376 * addr to specify to which of the local host's addresses should the
377 * external port be mapped. The port is taken from the corresponding
378 * sockaddr_in[6] field. The NAT module should call the given @a
379 * address_callback for any 'plausible' external address.
381 * @param cfg configuration to use
382 * @param proto protocol this is about, IPPROTO_TCP or IPPROTO_UDP
383 * @param adv_port advertised port (port we are either bound to or that our OS
384 * locally performs redirection from to our bound port).
385 * @param num_addrs number of addresses in @a addrs
386 * @param addrs list of local addresses packets should be redirected to
387 * @param addrlens actual lengths of the addresses in @a addrs
388 * @param address_callback function to call everytime the public IP address changes
389 * @param reversal_callback function to call if someone wants connection reversal from us,
390 * NULL if connection reversal is not supported
391 * @param callback_cls closure for callbacks
392 * @return NULL on error, otherwise handle that can be used to unregister
394 struct GNUNET_NAT_Handle *
395 GNUNET_NAT_register (const struct GNUNET_CONFIGURATION_Handle *cfg,
398 unsigned int num_addrs,
399 const struct sockaddr **addrs,
400 const socklen_t *addrlens,
401 GNUNET_NAT_AddressCallback address_callback,
402 GNUNET_NAT_ReversalCallback reversal_callback,
405 struct GNUNET_NAT_Handle *nh;
406 struct GNUNET_NAT_RegisterMessage *rm;
411 for (unsigned int i=0;i<num_addrs;i++)
413 if ( (len > GNUNET_SERVER_MAX_MESSAGE_SIZE - sizeof (*rm)) ||
414 (num_addrs > UINT16_MAX) )
419 rm = GNUNET_malloc (sizeof (*rm) + len);
420 rm->header.size = htons (sizeof (*rm) + len);
421 rm->header.type = htons (GNUNET_MESSAGE_TYPE_NAT_REGISTER);
422 rm->flags = GNUNET_NAT_RF_NONE;
423 if (NULL != address_callback)
424 rm->flags |= GNUNET_NAT_RF_ADDRESSES;
425 if (NULL != reversal_callback)
426 rm->flags |= GNUNET_NAT_RF_REVERSAL;
428 rm->adv_port = htons (adv_port);
429 rm->num_addrs = htons ((uint16_t) num_addrs);
430 off = (char *) &rm[1];
431 for (unsigned int i=0;i<num_addrs;i++)
433 switch (addrs[i]->sa_family)
436 if (sizeof (struct sockaddr_in) != addrlens[i])
443 if (sizeof (struct sockaddr_in6) != addrlens[i])
451 if (sizeof (struct sockaddr_un) != addrlens[i])
468 nh = GNUNET_new (struct GNUNET_NAT_Handle);
469 nh->reg = &rm->header;
471 nh->address_callback = address_callback;
472 nh->reversal_callback = reversal_callback;
473 nh->callback_cls = callback_cls;
480 * Check if an incoming message is a STUN message.
482 * @param data the packet
483 * @param len the length of the packet in @a data
484 * @return #GNUNET_YES if @a data is a STUN packet,
485 * #GNUNET_NO if the packet is invalid (not a stun packet)
488 test_stun_packet (const void *data,
491 const struct stun_header *hdr;
492 const struct stun_attr *attr;
493 uint32_t advertised_message_size;
494 uint32_t message_magic_cookie;
496 /* On entry, 'len' is the length of the UDP payload. After the
497 * initial checks it becomes the size of unprocessed options,
498 * while 'data' is advanced accordingly.
500 if (len < sizeof(struct stun_header))
502 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
503 "STUN packet too short (only %d, wanting at least %d)\n",
505 (int) sizeof (struct stun_header));
508 hdr = (const struct stun_header *) data;
509 /* Skip header as it is already in hdr */
510 len -= sizeof (struct stun_header);
511 data += sizeof (struct stun_header);
513 /* len as advertised in the message */
514 advertised_message_size = ntohs (hdr->msglen);
516 message_magic_cookie = ntohl (hdr->magic);
517 /* Compare if the cookie match */
518 if (STUN_MAGIC_COOKIE != message_magic_cookie)
520 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
521 "Invalid magic cookie for STUN\n");
525 if (advertised_message_size > len)
527 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
528 "Scrambled STUN packet length (got %d, expecting %d)\n",
529 advertised_message_size,
533 len = advertised_message_size;
536 if (len < sizeof (struct stun_attr))
538 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
539 "Attribute too short in STUN packet (got %d, expecting %d)\n",
541 (int) sizeof(struct stun_attr));
544 attr = (const struct stun_attr *) data;
546 /* compute total attribute length */
547 advertised_message_size = ntohs (attr->len) + sizeof(struct stun_attr);
549 /* Check if we still have space in our buffer */
550 if (advertised_message_size > len)
552 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
553 "Inconsistent Attribute (length %d exceeds remaining msg len %d)\n",
554 advertised_message_size,
558 data += advertised_message_size;
559 len -= advertised_message_size;
561 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
562 "STUN Packet, msg %04x, length: %d\n",
563 ntohs (hdr->msgtype),
564 advertised_message_size);
570 * Handle an incoming STUN message. This function is useful as
571 * some GNUnet service may be listening on a UDP port and might
572 * thus receive STUN messages while trying to receive other data.
573 * In this case, this function can be used to process replies
576 * The function does some basic sanity checks on packet size and
577 * content, try to extract a bit of information.
579 * At the moment this only processes BIND requests, and returns the
580 * externally visible address of the request to the rest of the
583 * @param nh handle to the NAT service
584 * @param sender_addr address from which we got @a data
585 * @param sender_addr_len number of bytes in @a sender_addr
586 * @param data the packet
587 * @param data_size number of bytes in @a data
588 * @return #GNUNET_OK on success
589 * #GNUNET_NO if the packet is not a STUN packet
590 * #GNUNET_SYSERR on internal error handling the packet
593 GNUNET_NAT_stun_handle_packet (struct GNUNET_NAT_Handle *nh,
594 const struct sockaddr *sender_addr,
595 size_t sender_addr_len,
599 struct GNUNET_MQ_Envelope *env;
600 struct GNUNET_NAT_HandleStunMessage *hsn;
604 test_stun_packet (data,
608 return GNUNET_SYSERR;
609 env = GNUNET_MQ_msg_extra (hsn,
610 data_size + sender_addr_len,
611 GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN);
612 hsn->sender_addr_size = htons ((uint16_t) sender_addr_len);
613 hsn->payload_size = htons ((uint16_t) data_size);
614 buf = (char *) &hsn[1];
618 buf += sender_addr_len;
622 GNUNET_MQ_send (nh->mq,
629 * Test if the given address is (currently) a plausible IP address for
630 * this peer. Mostly a convenience function so that clients do not
631 * have to explicitly track all IPs that the #GNUNET_NAT_AddressCallback
632 * has returned so far.
634 * @param nh the handle returned by register
635 * @param addr IP address to test (IPv4 or IPv6)
636 * @param addrlen number of bytes in @a addr
637 * @return #GNUNET_YES if the address is plausible,
638 * #GNUNET_NO if the address is not plausible,
639 * #GNUNET_SYSERR if the address is malformed
642 GNUNET_NAT_test_address (struct GNUNET_NAT_Handle *nh,
646 struct AddrEntry *ae;
648 if ( (addrlen != sizeof (struct sockaddr_in)) &&
649 (addrlen != sizeof (struct sockaddr_in6)) )
652 return GNUNET_SYSERR;
654 for (ae = nh->ae_head; NULL != ae; ae = ae->next)
655 if ( (addrlen == ae->addrlen) &&
665 * We learned about a peer (possibly behind NAT) so run the
666 * gnunet-nat-client to send dummy ICMP responses to cause
667 * that peer to connect to us (connection reversal).
669 * @param nh handle (used for configuration)
670 * @param local_sa our local address of the peer (IPv4-only)
671 * @param remote_sa the remote address of the peer (IPv4-only)
672 * @return #GNUNET_SYSERR on error,
673 * #GNUNET_NO if connection reversal is unavailable,
674 * #GNUNET_OK otherwise (presumably in progress)
677 GNUNET_NAT_request_reversal (struct GNUNET_NAT_Handle *nh,
678 const struct sockaddr_in *local_sa,
679 const struct sockaddr_in *remote_sa)
681 struct GNUNET_MQ_Envelope *env;
682 struct GNUNET_NAT_RequestConnectionReversalMessage *req;
686 return GNUNET_SYSERR;
687 env = GNUNET_MQ_msg_extra (req,
688 2 * sizeof (struct sockaddr_in),
689 GNUNET_MESSAGE_TYPE_NAT_REQUEST_CONNECTION_REVERSAL);
690 req->local_addr_size = htons (sizeof (struct sockaddr_in));
691 req->remote_addr_size = htons (sizeof (struct sockaddr_in));
692 buf = (char *) &req[1];
695 sizeof (struct sockaddr_in));
696 buf += sizeof (struct sockaddr_in);
699 sizeof (struct sockaddr_in));
700 GNUNET_MQ_send (nh->mq,
707 * Stop port redirection and public IP address detection for the given
708 * handle. This frees the handle, after having sent the needed
709 * commands to close open ports.
711 * @param nh the handle to stop
714 GNUNET_NAT_unregister (struct GNUNET_NAT_Handle *nh)
716 GNUNET_MQ_destroy (nh->mq);
717 GNUNET_free (nh->reg);
724 * Handle to auto-configuration in progress.
726 struct GNUNET_NAT_AutoHandle
730 * Configuration we use.
732 const struct GNUNET_CONFIGURATION_Handle *cfg;
735 * Message queue for communicating with the NAT service.
737 struct GNUNET_MQ_Handle *mq;
740 * Function called with the result from the autoconfiguration.
742 GNUNET_NAT_AutoResultCallback arc;
745 * Closure for @e arc.
753 * Converts `enum GNUNET_NAT_StatusCode` to string
755 * @param err error code to resolve to a string
756 * @return point to a static string containing the error code
759 GNUNET_NAT_status2string (enum GNUNET_NAT_StatusCode err)
763 case GNUNET_NAT_ERROR_SUCCESS:
764 return _ ("Operation Successful");
765 case GNUNET_NAT_ERROR_IPC_FAILURE:
766 return _ ("IPC failure");
767 case GNUNET_NAT_ERROR_INTERNAL_NETWORK_ERROR:
768 return _ ("Failure in network subsystem, check permissions.");
769 case GNUNET_NAT_ERROR_TIMEOUT:
770 return _ ("Encountered timeout while performing operation");
771 case GNUNET_NAT_ERROR_NOT_ONLINE:
772 return _ ("detected that we are offline");
773 case GNUNET_NAT_ERROR_UPNPC_NOT_FOUND:
774 return _ ("`upnpc` command not found");
775 case GNUNET_NAT_ERROR_UPNPC_FAILED:
776 return _ ("Failed to run `upnpc` command");
777 case GNUNET_NAT_ERROR_UPNPC_TIMEOUT:
778 return _ ("`upnpc' command took too long, process killed");
779 case GNUNET_NAT_ERROR_UPNPC_PORTMAP_FAILED:
780 return _ ("`upnpc' command failed to establish port mapping");
781 case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_NOT_FOUND:
782 return _ ("`external-ip' command not found");
783 case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_FAILED:
784 return _ ("Failed to run `external-ip` command");
785 case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_OUTPUT_INVALID:
786 return _ ("`external-ip' command output invalid");
787 case GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID:
788 return _ ("no valid address was returned by `external-ip'");
789 case GNUNET_NAT_ERROR_NO_VALID_IF_IP_COMBO:
790 return _ ("Could not determine interface with internal/local network address");
791 case GNUNET_NAT_ERROR_HELPER_NAT_SERVER_NOT_FOUND:
792 return _ ("No functioning gnunet-helper-nat-server installation found");
793 case GNUNET_NAT_ERROR_NAT_TEST_START_FAILED:
794 return _ ("NAT test could not be initialized");
795 case GNUNET_NAT_ERROR_NAT_TEST_TIMEOUT:
796 return _ ("NAT test timeout reached");
797 case GNUNET_NAT_ERROR_NAT_REGISTER_FAILED:
798 return _ ("could not register NAT");
799 case GNUNET_NAT_ERROR_HELPER_NAT_CLIENT_NOT_FOUND:
800 return _ ("No working gnunet-helper-nat-client installation found");
802 return "unknown status code";
808 * Check result from autoconfiguration attempt.
810 * @param cls the `struct GNUNET_NAT_AutoHandle`
811 * @param res the result
812 * @return #GNUNET_OK if @a res is well-formed (always for now)
815 check_auto_result (void *cls,
816 const struct GNUNET_NAT_AutoconfigResultMessage *res)
823 * Handle result from autoconfiguration attempt.
825 * @param cls the `struct GNUNET_NAT_AutoHandle`
826 * @param res the result
829 handle_auto_result (void *cls,
830 const struct GNUNET_NAT_AutoconfigResultMessage *res)
832 struct GNUNET_NAT_AutoHandle *ah = cls;
834 struct GNUNET_CONFIGURATION_Handle *cfg;
835 enum GNUNET_NAT_Type type
836 = (enum GNUNET_NAT_Type) ntohl (res->type);
837 enum GNUNET_NAT_StatusCode status
838 = (enum GNUNET_NAT_StatusCode) ntohl (res->status_code);
840 left = ntohs (res->header.size) - sizeof (*res);
841 cfg = GNUNET_CONFIGURATION_create ();
843 GNUNET_CONFIGURATION_deserialize (cfg,
844 (const char *) &res[1],
849 ah->arc (ah->arc_cls,
851 GNUNET_NAT_ERROR_IPC_FAILURE,
856 ah->arc (ah->arc_cls,
861 GNUNET_CONFIGURATION_destroy (cfg);
862 GNUNET_NAT_autoconfig_cancel (ah);
867 * Handle queue errors by reporting autoconfiguration failure.
869 * @param cls the `struct GNUNET_NAT_AutoHandle *`
870 * @param error details about the error
873 ah_error_handler (void *cls,
874 enum GNUNET_MQ_Error error)
876 struct GNUNET_NAT_AutoHandle *ah = cls;
878 ah->arc (ah->arc_cls,
880 GNUNET_NAT_ERROR_IPC_FAILURE,
881 GNUNET_NAT_TYPE_UNKNOWN);
882 GNUNET_NAT_autoconfig_cancel (ah);
887 * Start auto-configuration routine. The transport adapters should
888 * be stopped while this function is called.
890 * @param cfg initial configuration
891 * @param cb function to call with autoconfiguration result
892 * @param cb_cls closure for @a cb
893 * @return handle to cancel operation
895 struct GNUNET_NAT_AutoHandle *
896 GNUNET_NAT_autoconfig_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
897 GNUNET_NAT_AutoResultCallback cb,
900 struct GNUNET_NAT_AutoHandle *ah = GNUNET_new (struct GNUNET_NAT_AutoHandle);
901 struct GNUNET_MQ_MessageHandler handlers[] = {
902 GNUNET_MQ_hd_var_size (auto_result,
903 GNUNET_MESSAGE_TYPE_NAT_AUTO_CFG_RESULT,
904 struct GNUNET_NAT_AutoconfigResultMessage,
906 GNUNET_MQ_handler_end ()
908 struct GNUNET_MQ_Envelope *env;
909 struct GNUNET_NAT_AutoconfigRequestMessage *req;
913 buf = GNUNET_CONFIGURATION_serialize (cfg,
915 if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE - sizeof (*req))
923 ah->arc_cls = cb_cls;
924 ah->mq = GNUNET_CLIENT_connecT (cfg,
936 env = GNUNET_MQ_msg_extra (req,
938 GNUNET_MESSAGE_TYPE_NAT_REQUEST_AUTO_CFG);
939 GNUNET_memcpy (&req[1],
943 GNUNET_MQ_send (ah->mq,
950 * Abort autoconfiguration.
952 * @param ah handle for operation to abort
955 GNUNET_NAT_autoconfig_cancel (struct GNUNET_NAT_AutoHandle *ah)
957 GNUNET_MQ_destroy (ah->mq);
961 /* end of nat_api.c */