uIP 1.0
resolv.c
/**
 * \addtogroup apps
 * @{
 */

/**
 * \defgroup resolv DNS resolver - работа с именами DNS
 * @{
 *
 * Функции uIP DNS resolver используются для поиска имени хоста
 * (в базе данных имен DNS) и привязки его к цифровому адресу IP. 
 * Это поддерживает список распознанных имен хостов, который может
 * быть опрошен функцией resolv_lookup(). Новые имена хостов
 * могут быть распознаны с использованием функции resolv_query().
 *
 * Когда имя хоста было распознано (или найдено несуществующим),
 * код резолвера вызывает функцию обратного вызова (callback),
 * которая называется resolv_found(). Эта функция должна быть
 * реализована модулем, который использует резолвер.
 */

/**
 * \file
 * Резолвер - преобразователь имени DNS хоста в адрес IP.
 * \author Adam Dunkels <adam@dunkels.com>
 *
 * Этот файл реализует преобразователь DNS имени хоста в адрес IP.
 */

/*
 * Copyright (c) 2002-2003, Adam Dunkels.
 * Все права зарезервированы. *
 * Повторное распространение, использование в исходном и двоичном виде,
 * с модификацией или без - разрешается, если выполняются следующие
 * условия:
 * 1. Распространение исходного кода должно сохранить вышеуказанную пометку
 *    копирайта, этот список условий и следующую правовую оговорку.
 * 2. Распространение исходного кода должно сохранить вышеуказанную пометку
 *    копирайта, этот список условий и следующую правовую оговорку в
 *    документации и/или других материалах, которые будут предоставлены
 *    вместе с распространяемыми материалами.
 * 3. Имя автора не может использоваться, чтобы подтвердить или продвинуть
 *    продукты, написанные с использованием этого программного обеспечения
 *    без специального на то разрешения.
 *
 * ЭТО ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ АВТОРОМ ``КАК ЕСТЬ'', БЕЗ
 * КАКОЙ-ЛИБО ЛЮБОЙ РАСШИРЕННОЙ ИЛИ ПОДРАЗУМЕВАЕМОЙ ГАРАНТИИ, ВКЛЮЧАЯ,
 * НО НЕ ОГРАНИЧИВАЯСЬ ЭТИМ, ГАРАНТИИ ВЫСОКОГО СПРОСА И ПРИГОДНОСТИ
 * ДЛЯ КОНКРЕТНОЙ ЦЕЛИ. АВТОР НИ ПРИ КАКИХ УСЛОВИЯХ НЕ ОТВЕТСТВЕНЕН
 * ЗА ЛЮБЫЕ УБЫТКИ - ПРЯМЫЕ, КОСВЕННЫЕ, СЛУЧАЙНЫЕ, СПЕЦИАЛЬНЫЕ, ОБРАЗЦОВЫЕ
 * ИЛИ ПОСЛЕДОВАТЕЛЬНЫЕ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ЭТИМ, ТРЕБОВАНИЯ
 * ЗАМЕНЫ ТОВАРА ИЛИ СЕРВИСА; ПОТЕРИ ИСПОЛЬЗОВАНИЯ, ДАННЫХ ИЛИ ВЫГОДЫ;
 * ИЛИ ПРЕКРАЩЕНИЕ БИЗНЕСА), ОДНАКО ВЫЗВАННЫЕ ПО ЛЮБОЙ ТЕОРИИ ОТВЕТСТВЕННОСТИ,
 * ЛИБО В КОНТРАКТЕ, ПРЯМОЙ ОТВЕТСТВЕННОСТИ, ЛИБО В НАРУШЕНИИ ЗАКОННЫХ ПРАВ
 * (ВКЛЮЧАЯ ТАК ИЛИ ИНАЧЕ НЕБРЕЖНОСТЬ), ВОЗНИКАЮЩИЕ ВСЕГДА ИЗ ИСПОЛЬЗОВАНИЯ
 * ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, ДАЖЕ ЕСЛИ БЫЛО ПРЕДУПРЕЖДЕНИЕ О ВОЗМОЖНОСТИ
 * ТАКОГО ПОВРЕЖДЕНИЯ.
 *
 * Этот файл является частью стека uIP TCP/IP.
 *
 * $Id: resolv.c,v 1.5 2006/06/11 21:46:37 adam Exp $
 *
 */

#include "resolv.h"
#include "uip.h"

#include <string.h>

#ifndef NULL
#define NULL (void *)0
#endif /* NULL */

/** \internal Максимальное количество попыток запроса имени. */
#define MAX_RETRIES 8

/** \internal Заголовок сообщения DNS. */
struct dns_hdr {
  u16_t id;
  u8_t flags1, flags2;
#define DNS_FLAG1_RESPONSE        0x80
#define DNS_FLAG1_OPCODE_STATUS   0x10
#define DNS_FLAG1_OPCODE_INVERSE  0x08
#define DNS_FLAG1_OPCODE_STANDARD 0x00
#define DNS_FLAG1_AUTHORATIVE     0x04
#define DNS_FLAG1_TRUNC           0x02
#define DNS_FLAG1_RD              0x01
#define DNS_FLAG2_RA              0x80
#define DNS_FLAG2_ERR_MASK        0x0f
#define DNS_FLAG2_ERR_NONE        0x00
#define DNS_FLAG2_ERR_NAME        0x03
  u16_t numquestions;
  u16_t numanswers;
  u16_t numauthrr;
  u16_t numextrarr;
};

/** \internal Структура сообщения ответа DNS. */
struct dns_answer {
  /* Запись ответа DNS начинается либо с имени домена, либо
     с указателя на имя, где-то уже присутствующее в пакете. */
  u16_t type;
  u16_t class;
  u16_t ttl[2];
  u16_t len;
  uip_ipaddr_t ipaddr;
};

struct namemap {
#define STATE_UNUSED 0
#define STATE_NEW    1
#define STATE_ASKING 2
#define STATE_DONE   3
#define STATE_ERROR  4
  u8_t state;
  u8_t tmr;
  u8_t retries;
  u8_t seqno;
  u8_t err;
  char name[32];
  uip_ipaddr_t ipaddr;
};

#ifndef UIP_CONF_RESOLV_ENTRIES
#define RESOLV_ENTRIES 4
#else /* UIP_CONF_RESOLV_ENTRIES */
#define RESOLV_ENTRIES UIP_CONF_RESOLV_ENTRIES
#endif /* UIP_CONF_RESOLV_ENTRIES */


static struct namemap names[RESOLV_ENTRIES];

static u8_t seqno;

static struct uip_udp_conn *resolv_conn = NULL;


/*---------------------------------------------------------------------------*/
/** \internal
 * Просматривает компактно закодированное имя DNS и возвращает его конец.
 *
 * \return The end of the name.
 */
/*---------------------------------------------------------------------------*/
static unsigned char *
parse_name(unsigned char *query)
{
  unsigned char n;

  do {
    n = *query++;
    
    while(n > 0) {
      /*      printf("%c", *query);*/
      ++query;
      --n;
    };
    /*    printf(".");*/
  } while(*query != 0);
  /*  printf("\n");*/
  return query + 1;
}
/*---------------------------------------------------------------------------*/
/** \internal
 * Пробегает по списку имен, чтобы посмотреть, есть ли в списке имена,
 * которые еще не опрошены, и если так, то посылвает запрос.
 */
/*---------------------------------------------------------------------------*/
static void
check_entries(void)
{
  register struct dns_hdr *hdr;
  char *query, *nptr, *nameptr;
  static u8_t i;
  static u8_t n;
  register struct namemap *namemapptr;
  
  for(i = 0; i < RESOLV_ENTRIES; ++i) {
    namemapptr = &names[i];
    if(namemapptr->state == STATE_NEW ||
       namemapptr->state == STATE_ASKING) {
      if(namemapptr->state == STATE_ASKING) {
         if(--namemapptr->tmr == 0) {
            if(++namemapptr->retries == MAX_RETRIES) {
               namemapptr->state = STATE_ERROR;
               resolv_found(namemapptr->name, NULL);
               continue;
            }
            namemapptr->tmr = namemapptr->retries;
         } else {
            /*   printf("Timer %d\n", namemapptr->tmr);*/
            /* Его таймер не истек, так что перейдем
               к следующей записи. */
            continue;
         }
      } else {
         namemapptr->state = STATE_ASKING;
         namemapptr->tmr = 1;
         namemapptr->retries = 0;
      }
      hdr = (struct dns_hdr *)uip_appdata;
      memset(hdr, 0, sizeof(struct dns_hdr));
      hdr->id = htons(i);
      hdr->flags1 = DNS_FLAG1_RD;
      hdr->numquestions = HTONS(1);
      query = (char *)uip_appdata + 12;
      nameptr = namemapptr->name;
      --nameptr;
      /* Преобразует имя хоста в подходящий для запроса формат. */
      do {
         ++nameptr;
         nptr = query;
         ++query;
         for(n = 0; *nameptr != '.' && *nameptr != 0; ++nameptr) {
            *query = *nameptr;
            ++query;
            ++n;
         }
         *nptr = n;
      } while(*nameptr != 0);
      {
         static unsigned char endquery[] =
            {0,0,1,0,1};
         memcpy(query, endquery, 5);
      }
      uip_udp_send((unsigned char)(query + 5 - (char *)uip_appdata));
      break;
    }
  }
}
/*---------------------------------------------------------------------------*/
/** \internal
 * Вызывается, когда поступят новые данные UDP.
 */
/*---------------------------------------------------------------------------*/
static void
newdata(void)
{
  char *nameptr;
  struct dns_answer *ans;
  struct dns_hdr *hdr;
  static u8_t nquestions, nanswers;
  static u8_t i;
  register struct namemap *namemapptr;
  
  hdr = (struct dns_hdr *)uip_appdata;
  /*  printf("ID %d\n", htons(hdr->id));
      printf("Query %d\n", hdr->flags1 & DNS_FLAG1_RESPONSE);
      printf("Error %d\n", hdr->flags2 & DNS_FLAG2_ERR_MASK);
      printf("Num questions %d, answers %d, authrr %d, extrarr %d\n",
      htons(hdr->numquestions),
      htons(hdr->numanswers),
      htons(hdr->numauthrr),
      htons(hdr->numextrarr));
  */

  /* ID в заголовке DNS должен быть нашей записью в таблице имен. */
  i = htons(hdr->id);
  namemapptr = &names[i];
  if(i < RESOLV_ENTRIES &&
     namemapptr->state == STATE_ASKING) {

    /* Эта запись теперь завершена. */
    namemapptr->state = STATE_DONE;
    namemapptr->err = hdr->flags2 & DNS_FLAG2_ERR_MASK;

    /* Проверка на ошибку. Если так, то вызов callback для оповещения. */
    if(namemapptr->err != 0) {
      namemapptr->state = STATE_ERROR;
      resolv_found(namemapptr->name, NULL);
      return;
    }
   
    /* Мы заботимся только о вопросе (вопросах) и ответах. Так что authrr
       и extrarr просто отбрасываются. */
    nquestions = htons(hdr->numquestions);
    nanswers = htons(hdr->numanswers);
   
    /* Пропуск имени в вопросе. XXX: в действительности нужно проверить
       это имя, чтобы убедиться в соответствии. */
    nameptr = parse_name((char *)uip_appdata + 12) + 4;

    while(nanswers > 0) {
      /* Первый байт записи ответа ресурса определяет, сжатая ли тут
         запись или нормальная. */
      if(*nameptr & 0xc0) {
         /* Сжатое имя. */
         nameptr +=2;
         /* printf("Compressed anwser\n");*/
      } else {
         /* Не сжатое имя. */
         nameptr = parse_name((char *)nameptr);
      }

      ans = (struct dns_answer *)nameptr;
      /*      printf("Answer: type %x, class %x, ttl %x, length %x\n",
         htons(ans->type), htons(ans->class), (htons(ans->ttl[0])
            << 16) | htons(ans->ttl[1]), htons(ans->len));*/

      /* Проверка на тип адреса IP и Internet class. Другие
         отбрасываются. */
      if(ans->type == HTONS(1) &&
         ans->class == HTONS(1) &&
         ans->len == HTONS(4)) {
            /* printf("IP address %d.%d.%d.%d\n",
                     htons(ans->ipaddr[0]) >> 8,
                     htons(ans->ipaddr[0]) & 0xff,
                     htons(ans->ipaddr[1]) >> 8,
                     htons(ans->ipaddr[1]) & 0xff);*/
         /* XXX: мы действительно должны сделать проверку, что этот
            адрес IP тот, что нам нужен. */
         namemapptr->ipaddr[0] = ans->ipaddr[0];
         namemapptr->ipaddr[1] = ans->ipaddr[1];
   
         resolv_found(namemapptr->name, namemapptr->ipaddr);
         return;
      } else {
         nameptr = nameptr + 10 + htons(ans->len);
      }
      --nanswers;
    }
  }
}
/*---------------------------------------------------------------------------*/
/** \internal
 * Главная фукнция UDP.
 */
/*---------------------------------------------------------------------------*/
void
resolv_appcall(void)
{
  if(uip_udp_conn->rport == HTONS(53)) {
    if(uip_poll()) {
      check_entries();
    }
    if(uip_newdata()) {
      newdata();
    }
  }
}
/*---------------------------------------------------------------------------*/
/**
 * Ставит имя в очередь, так что будет отправлен запрос на имя.
 *
 * \param name Запрашиваемое имя хоста.
 */
/*---------------------------------------------------------------------------*/
void
resolv_query(char *name)
{
  static u8_t i;
  static u8_t lseq, lseqi;
  register struct namemap *nameptr;
      
  lseq = lseqi = 0;
  
  for(i = 0; i < RESOLV_ENTRIES; ++i) {
    nameptr = &names[i];
    if(nameptr->state == STATE_UNUSED) {
      break;
    }
    if(seqno - nameptr->seqno > lseq) {
      lseq = seqno - nameptr->seqno;
      lseqi = i;
    }
  }

  if(i == RESOLV_ENTRIES) {
    i = lseqi;
    nameptr = &names[i];
  }

  /*  printf("Using entry %d\n", i);*/

  strcpy(nameptr->name, name);
  nameptr->state = STATE_NEW;
  nameptr->seqno = seqno;
  ++seqno;
}
/*---------------------------------------------------------------------------*/
/**
 * Ищет имя хоста в массиве известных имен.
 *
 * \note Эта функция просматривает только внутренний массив известных
 * имен хостов, и не отправляет запрос, если имя хоста не найдено. Для
 * отправки запроса на распознание имени хоста может использоваться
 * функция resolv_query().
 *
 * \return Указатель на 4-байтное представление адреса IP хоста,
 * или NULL, если имя хоста не было найдено в массиве имен.
 */
/*---------------------------------------------------------------------------*/
u16_t *
resolv_lookup(char *name)
{
  static u8_t i;
  struct namemap *nameptr;
  
  /* Просмотр списка, чтобы узнать, есть ли в нем искомое имя. Если нет,
     то вернем NULL. */
  for(i = 0; i < RESOLV_ENTRIES; ++i) {
    nameptr = &names[i];
    if(nameptr->state == STATE_DONE &&
       strcmp(name, nameptr->name) == 0) {
      return nameptr->ipaddr;
    }
  }
  return NULL;
}
/*---------------------------------------------------------------------------*/
/**
 * Получение текущего сконфигурированного сервера DNS.
 *
 * \return Указатель на 4-байтное представление адреса IP текущего
 * сконфигурированного сервера DNS, или NULL, если сервер DNS не был
 * сконфигурирован.
 */
/*---------------------------------------------------------------------------*/
u16_t *
resolv_getserver(void)
{
  if(resolv_conn == NULL) {
    return NULL;
  }
  return resolv_conn->ripaddr;
}
/*---------------------------------------------------------------------------*/
/**
 * Конфигурирует сервер DNS, который будет использован для запросов.
 *
 * \param dnsserver Указатель на 4-байтное представление адреса IP сервера
 * DNS, который будет сконфигурирован.
 */
/*---------------------------------------------------------------------------*/
void
resolv_conf(u16_t *dnsserver)
{
  if(resolv_conn != NULL) {
    uip_udp_remove(resolv_conn);
  }
  
  resolv_conn = uip_udp_new(dnsserver, HTONS(53));
}
/*---------------------------------------------------------------------------*/
/**
 * Инициализирует распознаватель имен.
 */
/*---------------------------------------------------------------------------*/
void
resolv_init(void)
{
  static u8_t i;
  
  for(i = 0; i < RESOLV_ENTRIES; ++i) {
    names[i].state = STATE_DONE;
  }

}
/*---------------------------------------------------------------------------*/

/** @} */
/** @} */