/*
 * Copyright (c) 2002-2005 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 *	$Id: dnstsk.h,v 1.33 2007/11/05 05:48:20 ca Exp $
 */

#ifndef SM_DNS_TSK_H
#define SM_DNS_TSK_H 1

#include "sm/generic.h"
#include "sm/types.h"
#include "sm/limits.h"
#include "sm/time.h"
#include "sm/str.h"
#include "sm/bhtable.h"
#include "sm/dns.h"
#include "sm/log.h"
#if MTA_USE_PTHREADS
# include "sm/pthread.h"
# include "sm/evthr.h"
#endif

/*
**  Not defined on HP-UX by default? How to get it?  Should be in sm/net.h?
**  requires _XOPEN_SOURCE_EXTENDED?
*/

#ifndef MSG_WAITALL
# define MSG_WAITALL	0x40
#endif

#define LOCALHOST_IP	INADDR_LOOPBACK
#define DNS_PORT	53

/* maximum number of DNS tasks */
#define SM_DNS_MAX_TSKS	4u
#if SM_DNS_MAX_TSKS >= USHRT_MAX
# ERROR SM_DNS_MAX_TSKS _SM_DNS_MAX_TSKS >= USHRT_MAX _USHRT_MAX
#endif

/* Hash table size */
#define DNS_HT_SIZE	1023
#define DNS_HT_MAX	4095

/* Maximum length of query/answer (way too large?) */
#define MAX_QUERY_SIZE	(64 * 1024)


/* list of requests can be SIMPLEQ */
TAILQ_HEAD(dns_rql_S, dns_req_S);

typedef struct dns_tsk_S	dns_tsk_T, *dns_tsk_P;


/*
**  DNS Manager Context
**
**  The DNS manager uses a request list and a hash table to store DNS requests.
**  The (incoming) request list contains (in arriving order) the list of
**  unique requests.
**  The hash table contains all requests. It is used for two purposes:
**  1. to avoid querying a DNS resolver for the same item again if
**  there is already an open query.
**  2. to lookup requests after an answer is returned by a DNS
**  resolver.
**  The key for a hash table entry is the DNS query plus its type.
**
**  The incoming request list acts as a FIFO buffer between the
**  clients that send request to this DNS library and the DNS server:
**  requests from clients are appended to the list.
**  The wait list stores entries that are waiting for answers from a
**  DNS server. It might not be necessary to keep the list sorted in
**  case the list is short such that a search for expired entries is fast.
**
**  Since there will be only one request per query, different timeouts
**  don't work well. For example: ask for MX record for A with timeout 5s,
**  then ask for MX record for A with timeout 20s: the second request
**  will not be sent to a DNS server, hence if the requests times out
**  after 6 seconds, the second request will be treated as (temp) failed too
**  even though it might have succeeded.
*/

struct dns_mgr_ctx_S
{
	/* magic? */
	uint		 dnsmgr_flags;

	sm_log_ctx_P	 dnsmgr_lctx;	/* log context */

/* XXX should these be per DNS task? */
	/* sorted list of incoming requests */
	dns_rql_T	 dnsmgr_increq_hd;

	/* sorted list of requests waiting for answers */
	dns_rql_T	 dnsmgr_waitreq_hd;

	/* hash table to store all requests */
	bht_P		 dnsmgr_req_ht;

	ushort		 dnsmgr_timeout;	/* timeout for queries (s) */
	ushort		 dnsmgr_retries;

	ushort		 dnsmgr_ntsks;	/* number of DNS tasks */
	dns_tsk_P	 dnsmgr_dnstsks[SM_DNS_MAX_TSKS];
	ushort		 dnsmgr_tskstatus[SM_DNS_MAX_TSKS];

	/* last status change */
	time_T		 dnsmgr_tskchg[SM_DNS_MAX_TSKS];

#if MTA_USE_PTHREADS
	uint		 dnsmgr_ctsk;	/* current DNS task to use for query */
	sm_evthr_task_P	 dnsmgr_tsk[SM_DNS_MAX_TSKS];
	sm_evthr_task_P	 dnsmgr_cleanup;
	pthread_mutex_t	 dnsmgr_mutex;	/* for the entire context? */
#endif
};

/* states for DNS tasks, must be ordered */
#define DNSTSK_ST_NONE	0x0000u
#define DNSTSK_ST_INIT	0x0001u
#define DNSTSK_ST_OK	0x0002u
#define DNSTSK_ST_DEAD	0x0004u

/* CONF! */
#define DNSTSK_CHGTM		32

/*
**  DNS task context: this is bound to one DNS server
*/

struct dns_tsk_S
{
	/* XXX int or statethreads socket */
	int		 dnstsk_fd;		/* socket */
	dns_mgr_ctx_P	 dnstsk_mgr;		/* DNS manager */
	uint		 dnstsk_flags;		/* operating flags */

	/*
	**  counter for queries that timed out: if a limit is exceeded
	**  consider the server "dead" (unresponsive) and exclude it
	**  from the list
	*/

	uint		 dnstsk_timeouts;
	uint		 dnstsk_maxtimeouts;

	sockaddr_in_T	 dnstsk_sin;		/* socket description */

	sm_str_P	 dnstsk_rd;	/* read buffer */
	sm_str_P	 dnstsk_wr;	/* write buffer */

	ushort		 dnstsk_idx;	/* index of this DNS task */

	/* sorted list of incoming requests */
	dns_rql_T	 dnstsk_increq_hd;
	uint		 dnstsk_inc_reqs; /* number of incoming requests */
	uint		 dnstsk_open_reqs; /* number of open requests */
#if MTA_USE_PTHREADS
	pthread_mutex_t	 dnstsk_mutex;	/* for list of incoming requests */
#endif
};

/*
**  Locking order:
**	1. dns_mgr_ctx->dnsmgr_mutex
**	2. dns_tsk->dnstsk_mutex
*/

#define DNS_TSK_FL_USETCP	0x0001u		/* use TCP instead of UDP */
#define DNS_TSK_FL_CONNECTUDP	0x0002u		/* use connected UDP socket */
#define DNS_TSK_FL_OPT_MSK	0x000fu		/* option flag */
#define DNS_TSK_FL_HAS_MTX	0x0010u		/* mutex initialized */
#define DNS_TSK_FL_WR_EN	0x0020u		/* write enabled */
#define DNS_TSK_FL_SHUTDOWN	0x1000u		/* shutdown on next run */
/* 0x200u - 0x8000u are reserved! */

#define DNS_TSK_SET_FLAG(dns_tsk, fl) (dns_tsk)->dnstsk_flags |= (fl)
#define DNS_TSK_CLR_FLAG(dns_tsk, fl) (dns_tsk)->dnstsk_flags &= ~(fl)
#define DNS_TSK_IS_FLAG(dns_tsk, fl) (((dns_tsk)->dnstsk_flags & (fl)) != 0)

#define DNSTRQL_INIT(dns_tsk)	TAILQ_INIT(&((dns_tsk)->dnstsk_increq_hd))
#define DNSTRQL_FIRST(dns_tsk)	TAILQ_FIRST(&((dns_tsk)->dnstsk_increq_hd))
#define DNSTRQL_LAST(dns_tsk)	TAILQ_LAST(&((dns_tsk)->dnstsk_increq_hd), dns_rql_S)
#define DNSTRQL_EMPTY(dns_tsk)	TAILQ_EMPTY(&((dns_tsk)->dnstsk_increq_hd))
#define DNSTRQL_END(dns_tsk)	TAILQ_END(&((dns_tsk)->dnstsk_increq_hd))
#define DNSTRQL_NEXT(dns_increq)	TAILQ_NEXT(dns_increq, dnsreq_link)
#define DNSTRQL_PREV(dns_increq)	TAILQ_PREV(dns_increq, dns_rql_S, dnsreq_link)
#define DNSTRQL_INSERT_TAIL(dns_tsk, dns_increq)			\
	do {								\
		TAILQ_INSERT_TAIL(&((dns_tsk)->dnstsk_increq_hd),	\
				dns_increq, dnsreq_link);		\
		++(dns_tsk)->dnstsk_inc_reqs;				\
	} while (0)

#define DNSTRQL_REMOVE(dns_tsk, dns_increq)				\
	do {								\
		TAILQ_REMOVE(&((dns_tsk)->dnstsk_increq_hd),		\
				dns_increq, dnsreq_link);		\
		SM_ASSERT((dns_tsk)->dnstsk_inc_reqs > 0);		\
		--(dns_tsk)->dnstsk_inc_reqs;				\
	} while (0)


sm_ret_T dns_mgr_ctx_del(dns_mgr_ctx_P _dns_mgr_ctx);
sm_ret_T dns_mgr_ctx_new(uint _flags, uint _timeout, uint _htsize, uint _htlimit, dns_mgr_ctx_P *_pdns_mgr_ctx);
sm_ret_T dns_mgr_set_timeout(dns_mgr_ctx_P _dns_mgr_ctx, uint _timeout);
sm_ret_T dns_tsk_new(dns_mgr_ctx_P _dns_mgr_ctx, uint _flags, ipv4_T ipv4, dns_tsk_P *_pdns_tsk);
#if MTA_USE_PTHREADS
sm_ret_T dns_tsk_start(dns_mgr_ctx_P _dns_mgr_ctx, sm_evthr_ctx_P _evthr_ctx);
sm_ret_T dns_comm_tsk(sm_evthr_task_P _tsk);
#else
/* sm_ret_T dns_comm_tsk(sm_evthr_task_P _tsk); */
#endif
sm_ret_T dns_req_add(dns_mgr_ctx_P _dns_mgr_ctx, sm_cstr_P _query, dns_type_T _type, uint _timeout, dns_callback_F *_fct, void *_ctx);
void	 dns_req_del(void *_value, void *_key, void *_ctx);

sm_ret_T dns_receive(dns_tsk_P dns_tsk);
sm_ret_T dns_send(dns_tsk_P dns_tsk);

#endif /* ! SM_DNS_TSK_H */


syntax highlighted by Code2HTML, v. 0.9.1