/*********************************************************************************
|
* Copyright: (C) 2018 LingYun IoT Studio
|
* All rights reserved.
|
*
|
* Filename: dns_api.c
|
* Description: This file is DNS client API based on UDP socket
|
*
|
* Version: 1.0.0(10/29/2018)
|
* Author: Guo Wenxue <guowenxue@gmail.com>
|
* ChangeLog: 1, Release initial version on "2018-10-28 01:38:08 PM"
|
*
|
********************************************************************************/
|
|
#include <stdio.h>
|
#include <errno.h>
|
#include <string.h>
|
#include <time.h>
|
#include <stdlib.h>
|
#include <unistd.h>
|
#include <sys/types.h>
|
#include <sys/socket.h>
|
#include <netinet/in.h>
|
#include <arpa/inet.h>
|
|
#include "dns_api.h"
|
|
|
/*+-----------------------------------------------------------------------------------+
|
*| DNS Protocal packet format definition
|
*| Reference: https://www.ietf.org/rfc/rfc1035.txt
|
*| https://www2.cs.duke.edu/courses/fall16/compsci356/DNS/DNS-primer.pdf
|
*+-----------------------------------------------------------------------------------+
|
*/
|
|
/*
|
DNS packet format:
|
+---------------------+
|
| Header |
|
+---------------------+
|
| Question | the question for the name server
|
+---------------------+
|
| Answer | RRs answering the question
|
+---------------------+
|
| Authority | RRs pointing toward an authority
|
+---------------------+
|
| Additional | RRs holding additional information
|
+---------------------+
|
|
Header section format:
|
1 1 1 1 1 1
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| ID |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| QDCOUNT |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| ANCOUNT |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| NSCOUNT |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| ARCOUNT |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
|
Question section format:
|
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| |
|
/ QNAME /
|
/ /
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| QTYPE |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| QCLASS |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
Resource record format:
|
1 1 1 1 1 1
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| |
|
/ /
|
/ NAME /
|
| |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| TYPE |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| CLASS |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| TTL |
|
| |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
| RDLENGTH |
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
/ RDATA /
|
/ /
|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
*/
|
|
|
void dump_buf(const char *prompt, char *data, int len)
|
{
|
int i;
|
|
if(prompt)
|
{
|
printf("%s", prompt);
|
}
|
|
for(i=0; i<len; i++)
|
{
|
printf("%02x ", data[i]);
|
}
|
|
printf("\n");
|
}
|
|
int fill_dns_head(char *packet, int size)
|
{
|
unsigned short tmp;
|
int ofset = 0;
|
|
if( !packet || size<DNS_HEAD_MIN )
|
{
|
printf("Invalid input arguments in %s()\n", __FUNCTION__);
|
return -1;
|
}
|
|
/* DNS Head: Transaction ID */
|
srand((int)time(0));
|
tmp = (unsigned short)random();
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
/* DNS Head: Flags */
|
tmp = 0x0001; /* MSB */
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
/* DNS Head: Questions */
|
tmp = 0x0100; /* MSB */
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
/* DNS Head: Answer RRs */
|
tmp = 0x0000;
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
/* DNS Head: Authority RRs */
|
tmp = 0x0000;
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
/* DNS Head: Additional RRs */
|
tmp = 0x0000;
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
//dump_buf("DNS packet head: \n", packet, ofset);
|
|
return ofset;
|
}
|
|
int fill_dns_question_qtype_qclass(char *packet, int size)
|
{
|
unsigned short tmp;
|
int ofset = 0;
|
|
if( !packet || size<DNS_HEAD_MIN )
|
{
|
printf("Invalid input arguments in %s()\n", __FUNCTION__);
|
return -1;
|
}
|
|
/* DNS QType: 0x0001(A record) 0x0005(CNAME)*/
|
tmp = 0x0100; /* MSB */
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
/* DNS QClass, 0x0001: representing Internet addresses */
|
tmp = 0x0100; /* MSB */
|
memcpy(&packet[ofset], &tmp, 2);
|
ofset += 2;
|
|
//dump_buf("DNS packet tail: \n", packet, ofset);
|
|
return ofset;
|
}
|
|
/* www.baidu.com ==> 3www5baidu3com0 */
|
|
int fill_dns_question_qname(char *domain, char *packet, int size)
|
{
|
int ofset = 0;
|
const char *start;
|
char *ptr;
|
int len;
|
|
/* Check arguments */
|
if( !domain || !packet || size<strlen(domain) )
|
{
|
return -1;
|
}
|
|
start = domain;
|
while( (ptr=(strchr(start, '.'))) != NULL )
|
{
|
len=ptr-start; /* get segemnent length by '.' */
|
packet[ofset++] = len;
|
|
len = ptr-start;
|
strncpy(&packet[ofset], start, len); /* copy segemnent string */
|
ofset += len;
|
|
start = ptr + 1; /* let start point to next character follow '.' */
|
}
|
|
/* last segemnent by '.' is pointed by start */
|
len = strlen(domain)- (start-domain);
|
packet[ofset++] = len;
|
|
/* check strncpy buffer limits */
|
if(size-ofset < len)
|
len = size-ofset;
|
|
strncpy(&packet[ofset], start, len);
|
ofset += len;
|
|
packet[ofset++] = 0x00;
|
|
return ofset;
|
}
|
|
|
|
int query_dns(char *dns_server_ip, char *domain, char *ipaddr, int ipaddr_size)
|
{
|
int sockfd;
|
char msg_buf[512];
|
int ofset = 0;
|
int rv = -1;
|
short records;
|
struct sockaddr_in servaddr;
|
|
if(!domain || !ipaddr || strlen(domain)<=0 || ipaddr_size<IPADDR_LEN)
|
{
|
printf("Invalid input arguments\n");
|
return -1;
|
}
|
memset(ipaddr, 0, ipaddr_size);
|
|
|
/*+--------------------------------------------------------------------------------------------------------------
|
*| Parser baidu.com:
|
*| Send DNS Query Packet:
|
*| a7 c3 01 00 00 01 00 00 00 00 00 00 05 62 61 69 64 75 03 63 6f 6d 00 00 01 00 01
|
*|
|
*| Header section format:
|
*| Session ID(2B): A7 C3
|
*| Flag(2B): 01 00
|
*| QDCOUNT(2B): 01 00 1 entry in the question section
|
*| ANCOUNT(2B): 00 00 0 resource records in the answer section
|
*| NSCOUNT(2B): 00 00 0 resource records in the authority records section
|
*| ARCOUNT(2B): 00 00 0 resource records in the additional records section
|
*|
|
*| Question section format:
|
*| QNAME(nB): 05 62 61 69 64 75 03 63 6f 6d 00: 05baidu03com0
|
*| QTYPE(2B): 00 01 1: (A record)
|
*| QCLASS(2B): 00 01 1: (Internet addresses)
|
*+--------------------------------------------------------------------------------------------------------------
|
*/
|
|
rv=fill_dns_head(&msg_buf[ofset], sizeof(msg_buf)-ofset);
|
if( rv < 0 )
|
{
|
printf("Fill DNS head failure\n");
|
return -2;
|
}
|
ofset += rv;
|
|
rv = fill_dns_question_qname(domain, &msg_buf[ofset], sizeof(msg_buf)-ofset);
|
if(rv < 0)
|
{
|
printf("Fill DNS Question Qname failure\n");
|
return -2;
|
}
|
ofset += rv;
|
|
rv= fill_dns_question_qtype_qclass(&msg_buf[ofset], sizeof(msg_buf)-ofset);
|
if( rv < 0 )
|
{
|
printf("Fill DNS Question QType and QClass\n");
|
return -3;
|
}
|
ofset += rv;
|
|
//dump_buf("DNS packet : \n", msg_buf, ofset);
|
|
if ( (sockfd=socket(PF_INET, SOCK_DGRAM, 0)) < 0 )
|
{
|
printf("Create UDP socket failure: %s\n", strerror(errno));
|
return -4;
|
}
|
|
memset(&servaddr, 0, sizeof(servaddr));
|
servaddr.sin_family = AF_INET;
|
servaddr.sin_port = htons(53); /*DNS default port 53 */
|
servaddr.sin_addr.s_addr = inet_addr(dns_server_ip);
|
|
if( sendto(sockfd, msg_buf, ofset, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 )
|
{
|
printf("Send DNS packet to DNS server[%s] failure: %s\n", dns_server_ip, strerror(errno));
|
return -5;
|
}
|
|
if( (rv=recvfrom(sockfd, msg_buf, sizeof(msg_buf), 0, NULL, NULL) ) < 0 )
|
{
|
printf("Receive DNS reply from DNS server[%s] failure: %s\n", dns_server_ip, strerror(errno));
|
return -6;
|
}
|
|
/*+--------------------------------------------------------------------------------------------------------------
|
*| Receive DNS Server Reply Packet:
|
*| a7 c3 81 80 00 01 [00 02] 00 00 00 00 05 62 61 69 64 75 03 63 6f 6d 00 00 01 00 01
|
*| c0 0c 00 01 00 01 00 00 00 21 00 04 dc b5 39 d8 c0 0c 00 01 00 01 00 00 00 21 00 04 7b 7d 73 6e
|
*| Question Section: a7 c3 ... 00 01, but bytes[8:7] is reply resource entries, here is [00 02] so get 2 entries
|
*|
|
*| First resource
|
*| Name Pointer(2B): c0 0c
|
*| Type(2B): 01 00
|
*| Class: 01 00
|
*| TTL(4B): 00 00 00 21
|
*| RDLENGTH(2B): 00 04
|
*| IP address: dc b5 39 d8 "220.181.57.216"
|
*|
|
*| Second resource
|
*| Name Pointer(2B): c0 0c
|
*| Type(2B): 01 00
|
*| Class: 01 00
|
*| TTL(4B): 00 00 00 21
|
*| RDLENGTH(2B): 00 04
|
*| IP address: 7b 7d 73 6e "123.125.115.110"
|
*|
|
*+--------------------------------------------------------------------------------------------------------------
|
*/
|
|
//dump_buf("Recieve DNS reply: \n", msg_buf, rv);
|
records = *(short *)&msg_buf[7];
|
|
/* 1, Reply resource will followed by request packet
|
* 2, 2B Name Pointer + 2B Type+2B Class + 4B TTL + 2B RDlength = 12B */
|
ofset += 12;
|
|
snprintf(ipaddr, ipaddr_size, "%d.%d.%d.%d",msg_buf[ofset],msg_buf[ofset+1],msg_buf[ofset+2],msg_buf[ofset+3]);
|
#if 0
|
printf("DNS server[%s] resolve domain \"%s\" get %d records:\n", dns_server_ip, domain, records);
|
while( records-- )
|
{
|
printf("%d.%d.%d.%d\n",msg_buf[ofset],msg_buf[ofset+1],msg_buf[ofset+2],msg_buf[ofset+3]);
|
ofset += 16;
|
}
|
#endif
|
|
close(sockfd);
|
return 0;
|
}
|