sres_blocking.c 11.3 KB
Newer Older
1 2 3
/*
 * This file is part of the Sofia-SIP package
 *
Pekka Pessi's avatar
Pekka Pessi committed
4
 * Copyright (C) 2006 Nokia Corporation.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

Pekka Pessi's avatar
Pekka Pessi committed
25 26
/**@CFILE sres_blocking.c
 * @brief Blocking interface for Sofia DNS Resolver implementation.
27 28
 * 
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
Pekka Pessi's avatar
Pekka Pessi committed
29
 * @date Created: Fri Mar 24 15:23:08 EET 2006 ppessi
30 31 32 33 34 35 36 37
 */

#include "config.h"

#if HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
38 39 40 41 42 43 44
#else
#if defined(_WIN32)
typedef _int8 int8_t;
typedef unsigned _int8 uint8_t;
typedef unsigned _int16 uint16_t;
typedef unsigned _int32 uint32_t;
#endif
45
#endif
46

47
#if HAVE_NETINET_IN_H
Pekka Pessi's avatar
Pekka Pessi committed
48 49
#include <sys/types.h>
#include <sys/socket.h>
50 51
#include <netinet/in.h>
#endif
52

Pekka Pessi's avatar
Pekka Pessi committed
53 54 55
#if HAVE_WINSOCK2_H
#include <winsock2.h>
#include <ws2tcpip.h>
56
#define HAVE_SELECT 1
57 58 59
#else
#define SOCKET_ERROR   (-1)
#define INVALID_SOCKET ((sres_socket_t)-1)
Pekka Pessi's avatar
Pekka Pessi committed
60
#endif
61 62 63 64 65 66 67 68

typedef struct sres_blocking_s sres_blocking_t;
typedef struct sres_blocking_context_s sres_blocking_context_t;

#define SRES_CONTEXT_T struct sres_blocking_context_s
#define SRES_ASYNC_T struct sres_blocking_s

#include "sofia-resolv/sres.h"
69
#include "sofia-resolv/sres_async.h"
70
#include <sofia-sip/su_errno.h>
71 72 73

#if HAVE_POLL
#include <poll.h>
Pekka Pessi's avatar
Pekka Pessi committed
74
#elif HAVE_SYS_SELECT_H
75 76 77 78 79 80 81 82 83 84 85 86
#include <sys/select.h>
#endif

#include <stdlib.h>
#include <errno.h>

struct sres_blocking_s
{
  int              n_sockets;
#if HAVE_POLL
  struct pollfd    fds[SRES_MAX_NAMESERVERS];
#elif HAVE_SELECT
87
  struct { sres_socket_t fd; } fds[SRES_MAX_NAMESERVERS];
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
#else
#error No wait mechanism!
#endif
  sres_record_t ***return_records;  
};

struct sres_blocking_context_s
{
  int          ready;
  sres_resolver_t *resolver;
  sres_blocking_t *block;
  sres_query_t *query;
  sres_record_t ***return_records;  
};

static
int sres_blocking_update(sres_blocking_t *b,
105 106
			 sres_socket_t new_socket,
			 sres_socket_t old_socket)
107 108 109 110
{
  int i, N = b->n_sockets;

  if (old_socket == new_socket) {
111
    if (old_socket == INVALID_SOCKET) {
112 113 114 115 116
      free(b);      /* Destroy us */
    }
    return 0;
  }

117
  if (old_socket != INVALID_SOCKET) {
118
    for (i = 0; i < N; i++) {
119
      if (b->fds[i].fd == old_socket)
120 121 122 123 124 125 126
	break;
    }
    if (i == N)
      return -1;

    N--;
    b->fds[i].fd = b->fds[N].fd;
127
    b->fds[N].fd = INVALID_SOCKET;
128 129
#if HAVE_POLL
    b->fds[i].events = b->fds[N].events;
130
    b->fds[N].events = 0;
131
#endif
132 133 134 135

    b->n_sockets = N;
  }
  
136
  if (new_socket != INVALID_SOCKET) {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
    if (N == SRES_MAX_NAMESERVERS)
      return -1;
    b->fds[N].fd = new_socket;
#if HAVE_POLL
    b->fds[N].events = POLLIN;
#endif
    b->n_sockets = N + 1;
  }

  return 0;
}

static
int sres_blocking_complete(sres_blocking_context_t *c)
{
152
  while (!c->ready) {
153 154 155 156 157 158 159 160 161 162
    int n, i;
#if HAVE_POLL
    n = poll(c->block->fds, c->block->n_sockets, 500);
    if (n < 0) {
      c->ready = n;
    }
    else if (n == 0) {
      sres_resolver_timer(c->resolver, -1);
    }
    else for (i = 0; i < c->block->n_sockets; i++) {
163 164 165 166
      if (c->block->fds[i].revents | POLLERR)
	sres_resolver_error(c->resolver, c->block->fds[i].fd);
      if (c->block->fds[i].revents | POLLIN)
	sres_resolver_receive(c->resolver, c->block->fds[i].fd);
167 168 169 170 171 172 173 174 175 176 177 178
    }
#elif HAVE_SELECT
    fd_set readfds[1], errorfds[1];
    struct timeval timeval[1];

    FD_ZERO(readfds);
    FD_ZERO(errorfds);

    timeval->tv_sec = 0;
    timeval->tv_usec = 500000;

    for (i = 0, n = 0; i < c->block->n_sockets; i++) {
179 180
      FD_SET(c->block->fds[i].fd, readfds);
      FD_SET(c->block->fds[i].fd, errorfds);
181 182
      if (c->block->fds[i].fd >= n)
	n = c->block->fds[i].fd + 1;
183 184
    }

185 186 187 188
    n = select(n, readfds, NULL, errorfds, timeval);
  
    if (n <= 0)
      sres_resolver_timer(c->resolver, -1);
189
    else for (i = 0; n > 0 && i < c->block->n_sockets; i++) {
190 191 192 193 194 195
      if (FD_ISSET(c->block->fds[i].fd, errorfds))
        sres_resolver_error(c->resolver, c->block->fds[i].fd);
      else if (FD_ISSET(c->block->fds[i].fd, readfds))
	sres_resolver_receive(c->resolver, c->block->fds[i].fd);
      else
	continue;
196
      n--;
197
    }
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
#endif
  }

  return c->ready;
}

static
void sres_blocking_callback(sres_blocking_context_t *c, 
			    sres_query_t *query,
			    sres_record_t **answers)
{
  c->ready = 1;
  *c->return_records = answers;
}

static 
sres_blocking_t *sres_set_blocking(sres_resolver_t *res)
{
  sres_blocking_t *b;
  int i;

  b = sres_resolver_get_async(res, sres_blocking_update); 
  if (b)
    return b;

223 224 225 226
  /* Check if resolver is already in asynchronous mode */
  if (sres_resolver_get_async(res, NULL))
    return NULL;

227 228 229 230
  /* Create a synchronous (blocking) interface towards resolver */
  b = calloc(1, sizeof *b);

  for (i = 0; i < SRES_MAX_NAMESERVERS; i++)
231
    b->fds[i].fd = INVALID_SOCKET;
232 233 234 235 236 237 238 239
  
  if (!sres_resolver_set_async(res, sres_blocking_update, b, 0)) {
    free(b), b = NULL;
  }

  return b;
}

Pekka Pessi's avatar
Pekka Pessi committed
240
/** Return true (and set resolver in blocking mode) if resolver can block. */
241 242 243 244 245 246 247
int sres_is_blocking(sres_resolver_t *res)
{
  if (res == NULL)
    return 0;
  return sres_set_blocking(res) != NULL;
}

Pekka Pessi's avatar
Pekka Pessi committed
248 249 250 251 252 253 254 255 256 257 258 259
/**Send a DNS query, wait for response, return results.
 *
 * Sends a DNS query with specified @a type and @a domain to the DNS server,
 * if @a ignore_cache is not given or no records are found from cache. 
 * Function returns an error record with nonzero status if no response is
 * received from DNS server.
 *
 * @param res pointer to resolver object
 * @param type record type to search (or sres_qtype_any for any record)
 * @param domain domain name to query
 * @param ignore_cache ignore cached answers if nonzero
 * @param return_records return-value parameter for dns records
260 261 262 263
 *
 * @retval >0 if query was responded
 * @retval 0 if result was found from cache
 * @retval -1 upon error
Pekka Pessi's avatar
Pekka Pessi committed
264
 *
265 266 267 268 269
 * @ERRORS
 * @ERROR EFAULT @a res or @a domain point outside the address space
 * @ERROR ENAMETOOLONG @a domain is longer than SRES_MAXDNAME
 * @ERROR ENETDOWN no DNS servers configured
 * @ERROR ENOMEM memory exhausted
270
 * @ERROR EOPNOTSUPP  resolver @a res is in asynchronous mode 
271
 *
Pekka Pessi's avatar
Pekka Pessi committed
272 273 274 275 276 277
 * @sa sres_query(), sres_blocking_search()
 *
 * @note A blocking query converts a resolver object permanently into
 * blocking mode. If you need to make blocking and non-blocking queries, use
 * sres_resolver_copy() to make a separate resolver object for blocking
 * queries.
278
 */
279 280 281
int sres_blocking_query(sres_resolver_t *res,
			uint16_t type,
			char const *domain,
Pekka Pessi's avatar
Pekka Pessi committed
282
			int ignore_cache,
283 284 285 286 287 288
			sres_record_t ***return_records)
{
  sres_blocking_context_t c[1];
  sres_record_t **cached;

  if (return_records == NULL)
289
    return su_seterrno(EFAULT);
290

Pekka Pessi's avatar
Pekka Pessi committed
291 292
  *return_records = NULL;

293 294
  c->block = sres_set_blocking(res);
  if (c->block == NULL)
295
    return su_seterrno(EOPNOTSUPP); /* Resolver in asynchronous mode */ 
296

Pekka Pessi's avatar
Pekka Pessi committed
297 298 299 300 301 302
  if (!ignore_cache) {
    cached = sres_cached_answers(res, type, domain);
    if (cached) {
      *return_records = cached;
      return 0;
    }
303 304 305 306 307 308 309 310 311 312
  }

  c->ready = 0;
  c->resolver = res;
  c->return_records = return_records;
  c->query = sres_query(res, sres_blocking_callback, c, type, domain);

  return sres_blocking_complete(c);
}

Pekka Pessi's avatar
Pekka Pessi committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
/** Search DNS, return results.
 *
 * Search for @a name with specified @a type and @a name from the DNS server. 
 * If the @a name does not contain enought dots, the search domains are
 * appended to the name and resulting domain name are also queried.
 *
 * @param res pointer to resolver object
 * @param type record type to search (or sres_qtype_any for any record)
 * @param name host or domain name to search from DNS
 * @param ignore_cache ignore cached answers if nonzero
 * @param return_records return-value parameter for dns records
 *
 * @retval >0 if query was responded
 * @retval 0 if result was found from cache
 * @retval -1 upon error
 *
329 330 331 332 333
 * @ERRORS
 * @ERROR EFAULT @a res or @a domain point outside the address space
 * @ERROR ENAMETOOLONG @a domain is longer than SRES_MAXDNAME
 * @ERROR ENETDOWN no DNS servers configured
 * @ERROR ENOMEM memory exhausted
334
 * @ERROR EOPNOTSUPP  resolver @a res is in asynchronous mode 
335
 *
Pekka Pessi's avatar
Pekka Pessi committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
 * @sa sres_blocking_query(), sres_search()
 *
 * @note A blocking query converts a resolver object permanently into
 * blocking mode. If you need to make blocking and non-blocking queries, use
 * sres_resolver_copy() to make a separate resolver object for blocking
 * queries.
 */
int sres_blocking_search(sres_resolver_t *res,
			 uint16_t type,
			 char const *name,
			 int ignore_cache,
			 sres_record_t ***return_records)
{
  sres_blocking_context_t c[1];
  sres_record_t **cached;

  if (return_records == NULL)
353
    return su_seterrno(EFAULT);
Pekka Pessi's avatar
Pekka Pessi committed
354 355 356 357 358

  *return_records = NULL;

  c->block = sres_set_blocking(res);
  if (c->block == NULL)
359
    return su_seterrno(EOPNOTSUPP); /* Resolver in asynchronous mode */ 
Pekka Pessi's avatar
Pekka Pessi committed
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

  if (!ignore_cache) {
    cached = sres_search_cached_answers(res, type, name);
    if (cached) {
      *return_records = cached;
      return 0;
    }
  }

  c->ready = 0;
  c->resolver = res;
  c->return_records = return_records;
  c->query = sres_search(res, sres_blocking_callback, c, type, name);

  return sres_blocking_complete(c);
}

377
/** Send a a reverse DNS query, return results.
Pekka Pessi's avatar
Pekka Pessi committed
378 379 380 381 382
 *
 * Sends a reverse DNS query with specified @a type and @a domain to the DNS
 * server if @a ignore_cache is not given or no cached records are found from
 * the cache. Function returns an error record with nonzero status if no
 * response is received from DNS server.
383 384 385 386
 *
 * @retval >0 if query was responded
 * @retval 0 if result was found from cache
 * @retval -1 upon error
Pekka Pessi's avatar
Pekka Pessi committed
387
 *
388 389 390 391
 * @ERRORS
 * @ERROR EFAULT @a res or @a addr point outside the address space
 * @ERROR ENOMEM memory exhausted
 * @ERROR ENETDOWN no DNS servers configured
392
 * @ERROR EOPNOTSUPP  resolver @a res is in asynchronous mode 
393 394
 *
 * @sa sres_blocking_query(), sres_query_sockaddr(), sres_cached_answers_sockaddr()
Pekka Pessi's avatar
Pekka Pessi committed
395 396 397 398 399
 *
 * @note A blocking query converts a resolver object permanently into
 * blocking mode. If you need to make blocking and non-blocking queries, use
 * sres_resolver_copy() to make a separate resolver object for blocking
 * queries.
400
 */
401 402 403
int sres_blocking_query_sockaddr(sres_resolver_t *res,
				 uint16_t type,
				 struct sockaddr const *addr,
Pekka Pessi's avatar
Pekka Pessi committed
404
				 int ignore_cache,
405 406 407 408 409 410 411
				 sres_record_t ***return_records)
{
  sres_blocking_context_t c[1];
  sres_record_t **cached;

  if (return_records == NULL)
    return errno = EFAULT, -1;
Pekka Pessi's avatar
Pekka Pessi committed
412 413 414

  *return_records = NULL;

415 416
  c->block = sres_set_blocking(res);
  if (c->block == NULL)
417
    return su_seterrno(EOPNOTSUPP); /* Resolver in asynchronous mode */ 
418

Pekka Pessi's avatar
Pekka Pessi committed
419 420 421 422 423 424
  if (!ignore_cache) {
    cached = sres_cached_answers_sockaddr(res, type, addr);
    if (cached) {
      *return_records = cached;
      return 0;
    }
425 426 427 428 429 430 431 432 433
  }

  c->ready = 0;
  c->resolver = res;
  c->return_records = return_records;
  c->query = sres_query_sockaddr(res, sres_blocking_callback, c, type, addr);

  return sres_blocking_complete(c);
}