/******************************************************************************* * libretroshare/src/util: rsdnsutils.cc * * * * libretroshare: retroshare core library * * * * Copyright 2021 Phenom * * * * This program 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 3 of the * * License, or (at your option) any later version. * * * * This program 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 program. If not, see . * * * *******************************************************************************/ //#define DEBUG_SPEC_DNS 1 #include "util/rsnet.h" #include "util/rsdebug.h" #include "util/rsthreads.h" #include "util/rsstring.h" #ifdef WINDOWS_SYS #else #include #endif #ifdef __APPLE__ #include #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070 static size_t strnlen(const char *s, size_t maxlength) { size_t l = 0; while (l < maxlength && *s) { l++; s++; } return l; } #endif #endif //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 constexpr uint16_t DNSC_IN = 1; //Internet (IN) //Need to pack as we use sizeof them (avoid padding) #pragma pack(1) //DNS header structure struct DNS_HEADER { unsigned short id; // identification number //Header flags https://www.bind9.net/dns-header-flags unsigned char rd :1; //bit 07 Recursion Desired, indicates if the client means a recursive query unsigned char tc :1; //bit 06 TrunCation, indicates that this message was truncated due to excessive length unsigned char aa :1; //bit 05 Authoritative Answer, in a response, indicates if the DNS server is authoritative for the queried hostname unsigned char opcode :4;//bit 01-04 The type can be QUERY (standard query, 0), IQUERY (inverse query, 1), or STATUS (server status request, 2) unsigned char qr :1; //bit 00 Indicates if the message is a query (0) or a reply (1) unsigned char rcode :4; //bit 12-15 Response Code can be NOERROR (0), FORMERR (1, Format error), SERVFAIL (2), NXDOMAIN (3, Nonexistent domain), etc. unsigned char cd :1; //bit 11 Checking Disabled [RFC 4035][RFC 6840][RFC Errata 4927] used by DNSSEC unsigned char ad :1; //bit 10 Authentic Data [RFC 4035][RFC 6840][RFC Errata 4924] used by DNSSEC unsigned char z :1; //bit 09 Zero, reserved for future use unsigned char ra :1; //bit 08 Recursion Available [RFC 1035] in a response, indicates if the replying DNS server supports recursion unsigned short q_count; // number of question entries unsigned short ans_count; // number of answer entries unsigned short auth_count; // number of authority resource records entries unsigned short add_count; // number of additional resource record entries }; //// OpCode text https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5 //static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS", // "RESERVED3", "NOTIFY", "UPDATE", // "STATEFUL", "RESERVED7", "RESERVED8", // "RESERVED9", "RESERVED10", "RESERVED11", // "RESERVED12", "RESERVED13", "RESERVED14", // "RESERVED15" }; //// RCode text https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 //static const char *rcodetext[] = { "NOERROR", "FORMERR", "SERVFAIL", // "NXDOMAIN", "NOTIMP", "REFUSED", // "YXDOMAIN", "YXRRSET", "NXRRSET", // "NOTAUTH", "NOTZONE", "DSOTYPENI", // "RESERVED12", "RESERVED13", "RESERVED14", // "RESERVED15", "BADVERS", "BADKEY", // "BADTIME", "BADMODE", "BADNAME", // "BADALG", "BADTRUNC", "BADCOOKIE"}; //Constant sized fields of query structure //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml struct QUESTION { unsigned short qtype; //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 unsigned short qclass; //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 }; //Constant sized fields of the resource record structure struct RR_DATA { unsigned short rtype; //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 unsigned short rclass; //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 unsigned int rttl; //Time To Live is the number of seconds left before the information expires (32-bits integer, so maximum 140 years) unsigned short data_len;//Lenght of following data }; #pragma pack() bool hostnameLength(size_t & retlength, const unsigned char* hostname, size_t maxlength){ size_t len1 = strnlen((const char*)hostname, maxlength); //According to terminating 0 size_t len2 = 0; //According to label sizes while((len2 < maxlength) && (hostname[len2] != '\0')) { len2 += hostname[len2] + 1; } if (len2 >= maxlength) { RS_ERR("Hostname is too long >", maxlength, " chars: ", len2); return false; } if(len1 == len2) { retlength = len2 + 1; //Terminating 0 return true; }else{ RS_ERR("Hostname length mismatch, the DNS server sent some mallicious stuff ", len1, " ", len2); return false; } } bool rsGetHostByNameSpecDNS(const std::string& servername, const std::string& hostname, std::string& returned_addr, int timeout_s){ return rsGetRecordByNameSpecDNS(servername, 53, hostname, DNST_A_or_AAAA, returned_addr, timeout_s); } bool rsGetRecordByNameSpecDNS(const std::string& servername, unsigned short serverport, const std::string& hostname, unsigned short recordtype, std::string& returned_addr, int timeout_s /*= -1*/) { #ifdef DEBUG_SPEC_DNS RS_DBG("servername=", servername, " hostname=", hostname); #endif if (strlen(servername.c_str()) > 256) { RS_ERR("servername is too long > 256 chars: ", servername); return false; } if (strlen(hostname.c_str()) > 256) { RS_ERR("hostname is too long > 256 chars: ", hostname); return false; } sockaddr_storage serverAddr; bool validServer = sockaddr_storage_inet_pton(serverAddr, servername) && sockaddr_storage_isValidNet(serverAddr); bool isIPV4 = validServer && sockaddr_storage_ipv6_to_ipv4(serverAddr); if (!validServer) { in_addr in ; if (!rsGetHostByName(servername, in)) { RS_ERR("servername is on an unknow format: ", servername); return false; } validServer = sockaddr_storage_inet_pton(serverAddr, rs_inet_ntoa(in)) && sockaddr_storage_isValidNet(serverAddr); isIPV4 = validServer && sockaddr_storage_ipv6_to_ipv4(serverAddr); if (!validServer) { RS_ERR("rsGetHostByName return bad answer: ", rs_inet_ntoa(in)); return false; } } sockaddr_storage_setport( serverAddr, serverport); //Set the DNS structure to standard queries unsigned char buf[65536]; struct DNS_HEADER* dns = (struct DNS_HEADER *)&buf; dns->id = static_cast(htons(getpid())); //Transaction Id //dns flags = 0x0100 Standard Query dns->rd = 1; //Recursion Desired: Do query recursively dns->tc = 0; //TrunCated: Message is not truncated dns->aa = 0; //Authoritative: Server is not an authority for domain dns->opcode = 0; //OpCode: Standard query dns->qr = 0; //Query/Response: Message is a query dns->rcode = 0; //Response Code: No error dns->cd = 0; //Checking Disabled: Unacceptable dns->ad = 0; //Authentic Data: Answer/authority portion was not authenticated by the server dns->z = 0; //Z: reserved dns->ra = 0; //Recursion Available: Server cannot do recursive queries dns->q_count = htons(1); //1 Question dns->ans_count = 0; //0 Answer dns->auth_count = 0; //0 Authority RRs dns->add_count = 0; //0 Additional RRs size_t curSendSize = sizeof(struct DNS_HEADER); //Point to the query server name portion unsigned char* qname =static_cast(&buf[curSendSize]); size_t qnameSize = strlen(hostname.c_str()) + 1; //+1 for the leading byte memcpy(&qname[1],hostname.c_str(), qnameSize); //Copy the last 0 too // Format Hostname like www.google.com to 3www6google3com { size_t last = qnameSize; for(size_t i = qnameSize-1 ; i > 0 ; i--) if(qname[i]=='.') { size_t labelsize = last-i-1; if(labelsize > 63){ RS_ERR("Labelsize is too long: ", labelsize); return false; } qname[i]=labelsize; last = i; } //First byte size_t labelsize = last-1; if(labelsize > 63){ RS_ERR("Labelsize is too long: ", labelsize); return false; } //First byte is Label Type: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-10 qname[0] = labelsize; //One Label with Normal label lower 6 bits is the length of the label } curSendSize += qnameSize +1; //With \0 terminator //Point to the query constant portion struct QUESTION* qinfo =(struct QUESTION*)&buf[curSendSize]; if(recordtype == DNST_A_or_AAAA) qinfo->qtype = htons(isIPV4 ? DNST_A : DNST_AAAA); //Type: A / AAAA(Host Address) else qinfo->qtype = htons(recordtype); qinfo->qclass = htons(DNSC_IN); //Class: IN curSendSize += sizeof(struct QUESTION); #ifdef DEBUG_SPEC_DNS RS_DBG("Sending Packet:\n", hexDump(buf, curSendSize)); #endif int s = socket(serverAddr.ss_family , SOCK_DGRAM , IPPROTO_UDP); //UDP packet for DNS queries if(s<0) { RS_ERR("Could not open socket."); return false; } if (timeout_s > -1) rs_setSockTimeout(s, true, timeout_s); ssize_t send_size = sendto( s, (char*)buf, curSendSize, 0 , (struct sockaddr*)&serverAddr , isIPV4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6) ); if( send_size < 0) { RS_ERR("Send Failed with size = ", send_size); #ifndef WINDOWS_SYS close(s); #else closesocket(s); #endif return false; } #ifdef DEBUG_SPEC_DNS RS_DBG("Waiting answer..."); #endif //****************************************************************************************// //--- Receive the answer ---// //****************************************************************************************// socklen_t sa_size = static_cast(isIPV4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6) ); ssize_t rec_size=recvfrom( s,(char*)buf , 65536 , 0 , (struct sockaddr*)&serverAddr , &sa_size ); #ifndef WINDOWS_SYS close(s); // No more need of this socket, close it. #else closesocket(s); #endif if(rec_size <= 0) { RS_ERR("Receive Failed"); return false; } #ifdef DEBUG_SPEC_DNS RS_DBG("Received:\n", hexDump(buf, rec_size)); #endif if (rec_size< static_cast(sizeof(struct DNS_HEADER)) ) { RS_ERR("Request received too small to get DNSHeader."); return false; } #ifdef DEBUG_SPEC_DNS //Point to the header portion dns = (struct DNS_HEADER*) buf; RS_DBG("The response contains :\n" ,ntohs(dns->q_count) , " Questions.\n" ,ntohs(dns->ans_count) , " Answers.\n" ,ntohs(dns->auth_count) , " Authoritative Servers.\n" ,ntohs(dns->add_count) , " Additional records."); #endif size_t curRecSize = sizeof(struct DNS_HEADER); if (rec_size< static_cast(curRecSize + 1 + sizeof(struct QUESTION)) ) { RS_ERR("Request received too small to get Question return."); return false; } //Point to the query portion unsigned char* qnameRecv =static_cast(&buf[curRecSize]); if (memcmp(qname,qnameRecv,qnameSize + 1 + sizeof(struct QUESTION)) ) { RS_ERR("Request received different from that sent."); return false; } curRecSize += qnameSize + 1 + sizeof(struct QUESTION); for(int i=0; ians_count); i++) { if (rec_size< static_cast(curRecSize + 2) ) { RS_ERR("Request received too small to get Answer return."); return false; } //Point to the Answer portion unsigned char* reader = &buf[curRecSize]; size_t rLabelSize=0; if((reader[0]&0xC0) == 0) { //Normal label lower 6 bits is the length of the label if(!hostnameLength(rLabelSize, reader, rec_size - curRecSize)) return false; } else if ((reader[0]&0xC0) == 0xC0) { //Compressed label the lower 6 bits and the 8 bits from next octet form a pointer to the compression target. //Don't need to read it, maybe the same as in Query rLabelSize=2; } else { RS_ERR("Answer received with unmanaged label format."); return false; } curRecSize += rLabelSize; if (rec_size< static_cast(curRecSize + sizeof(struct RR_DATA)) ) { RS_ERR("Request received too small to get Data return."); return false; } //Point to the query portion struct RR_DATA* rec_data = (struct RR_DATA *)&buf[curRecSize]; if (rec_data->rclass!=qinfo->qclass) { RS_ERR("Answer's class received different from query sent."); return false; } curRecSize += sizeof(struct RR_DATA); unsigned short data_len = ntohs(rec_data->data_len); unsigned short rtype = ntohs(rec_data->rtype); if (rec_data->rtype!=qinfo->qtype) { RS_INFO("Answer's type received different from query sent, skipping answer."); curRecSize += data_len; continue; //Skip to the next answer record } if (rec_size< static_cast(curRecSize + data_len) ) { RS_ERR("Request received too small to get Full Data return."); return false; } //Retrieve Address if(data_len==4 && rtype==DNST_A) { in_addr ipv4Add; ipv4Add.s_addr=*(in_addr_t*)&buf[curRecSize]; #ifdef DEBUG_SPEC_DNS RS_DBG("Retrieve address: ", rs_inet_ntoa(ipv4Add)); #endif returned_addr = rs_inet_ntoa(ipv4Add); return true; } else if(data_len==16 && rtype==DNST_AAAA) { in6_addr ipv6Add; ipv6Add =*(in6_addr*)&buf[curRecSize]; struct sockaddr_storage ss; sockaddr_storage_clear(ss); sockaddr_in6 addr_ipv6; addr_ipv6.sin6_addr = ipv6Add; sockaddr_storage_setipv6(ss,&addr_ipv6); #ifdef DEBUG_SPEC_DNS RS_DBG("Retrieve address: ", sockaddr_storage_iptostring(ss).c_str()); #endif returned_addr = sockaddr_storage_iptostring(ss); return true; } else if(rtype==DNST_CNAME && data_len >=2 && ((buf[curRecSize]&0xC0) == 0)) //Make sure the hostname is not empty, and not compressed { size_t len=0; if(hostnameLength(len, &buf[curRecSize], data_len) && len==data_len) //Validate hostname length { size_t pos = curRecSize; //the hostname is already validated by hostnameLength while(buf[pos] != '\0') //so we can safely decode it in place { size_t prevpos = pos; pos += buf[pos] + 1; buf[prevpos] = '.'; } std::string decodedname((char*)&buf[curRecSize+1]); // +1 skip the first dot returned_addr = decodedname; #ifdef DEBUG_SPEC_DNS RS_DBG("Retrieve CNAME: ", decodedname.c_str()); #endif return true; }else{ RS_ERR("Retrieved hostname length != datalength, expected length: ", data_len, ", actual length: ", len); return false; } } else { RS_ERR("Received unknown record type: ", rtype, ", length: ", data_len); return false; } curRecSize += data_len; //Check the next answer record } RS_ERR("Not received answer for the question"); return false; }