/ ESP32S2_TFT_AdBlocker / AdBlockerDNSServer.cpp
AdBlockerDNSServer.cpp
1 // SPDX-FileCopyrightText: 2022 s60sc with changes by ladyada 2 // SPDX-License-Identifier: GPL-3.0-or-later 3 4 #include "AdBlockerDNSServer.h" 5 #include <lwip/def.h> 6 #include <Arduino.h> 7 8 9 //// ESP32_AdBlocker 10 // call back to check if domain blocked and receive IP address to be used 11 IPAddress checkBlocklist(const char* domainName); 12 //// ESP32_AdBlocker 13 14 DNSServer::DNSServer() 15 { 16 _ttl = htonl(DNS_DEFAULT_TTL); 17 _errorReplyCode = DNSReplyCode::NonExistentDomain; 18 _dnsHeader = (DNSHeader*) malloc( sizeof(DNSHeader) ) ; 19 _dnsQuestion = (DNSQuestion*) malloc( sizeof(DNSQuestion) ) ; 20 _buffer = NULL; 21 _currentPacketSize = 0; 22 _port = 0; 23 } 24 25 bool DNSServer::start(const uint16_t &port, const String &domainName, 26 const IPAddress &resolvedIP) 27 { 28 _port = port; 29 _buffer = NULL; 30 _domainName = domainName; 31 _resolvedIP[0] = resolvedIP[0]; 32 _resolvedIP[1] = resolvedIP[1]; 33 _resolvedIP[2] = resolvedIP[2]; 34 _resolvedIP[3] = resolvedIP[3]; 35 downcaseAndRemoveWwwPrefix(_domainName); 36 return _udp.begin(_port) == 1; 37 } 38 39 void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode) 40 { 41 _errorReplyCode = replyCode; 42 } 43 44 void DNSServer::setTTL(const uint32_t &ttl) 45 { 46 _ttl = htonl(ttl); 47 } 48 49 void DNSServer::stop() 50 { 51 _udp.stop(); 52 free(_buffer); 53 _buffer = NULL; 54 } 55 56 void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName) 57 { 58 domainName.toLowerCase(); 59 domainName.replace("www.", ""); 60 } 61 62 void DNSServer::processNextRequest() 63 { 64 _currentPacketSize = _udp.parsePacket(); 65 if (_currentPacketSize) 66 { 67 // Allocate buffer for the DNS query 68 if (_buffer != NULL) 69 free(_buffer); 70 _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char)); 71 if (_buffer == NULL) 72 return; 73 74 // Put the packet received in the buffer and get DNS header (beginning of message) 75 // and the question 76 _udp.read(_buffer, _currentPacketSize); 77 memcpy( _dnsHeader, _buffer, DNS_HEADER_SIZE ) ; 78 if ( requestIncludesOnlyOneQuestion() ) 79 { 80 // The QName has a variable length, maximum 255 bytes and is comprised of multiple labels. 81 // Each label contains a byte to describe its length and the label itself. The list of 82 // labels terminates with a zero-valued byte. In "github.com", we have two labels "github" & "com" 83 // Iterate through the labels and copy them as they come into a single buffer (for simplicity's sake) 84 _dnsQuestion->QNameLength = 0 ; 85 while ( _buffer[ DNS_HEADER_SIZE + _dnsQuestion->QNameLength ] != 0 ) 86 { 87 memcpy( (void*) &_dnsQuestion->QName[_dnsQuestion->QNameLength], (void*) &_buffer[DNS_HEADER_SIZE + _dnsQuestion->QNameLength], _buffer[DNS_HEADER_SIZE + _dnsQuestion->QNameLength] + 1 ) ; 88 _dnsQuestion->QNameLength += _buffer[DNS_HEADER_SIZE + _dnsQuestion->QNameLength] + 1 ; 89 } 90 _dnsQuestion->QName[_dnsQuestion->QNameLength] = 0 ; 91 _dnsQuestion->QNameLength++ ; 92 93 // Copy the QType and QClass 94 memcpy( &_dnsQuestion->QType, (void*) &_buffer[DNS_HEADER_SIZE + _dnsQuestion->QNameLength], sizeof(_dnsQuestion->QType) ) ; 95 memcpy( &_dnsQuestion->QClass, (void*) &_buffer[DNS_HEADER_SIZE + _dnsQuestion->QNameLength + sizeof(_dnsQuestion->QType)], sizeof(_dnsQuestion->QClass) ) ; 96 } 97 98 99 if (_dnsHeader->QR == DNS_QR_QUERY && 100 _dnsHeader->OPCode == DNS_OPCODE_QUERY && 101 requestIncludesOnlyOneQuestion() && 102 (_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName) 103 ) 104 { 105 //// ESP32_AdBlocker 106 IPAddress IPtoUse = checkBlocklist(getDomainNameWithoutWwwPrefix().c_str()); 107 for (int i=0; i<4; i++) _resolvedIP[i] = IPtoUse[i]; 108 //// ESP32_AdBlocker 109 110 replyWithIP(); 111 } 112 else if (_dnsHeader->QR == DNS_QR_QUERY) 113 { 114 replyWithCustomCode(); 115 } 116 117 free(_buffer); 118 _buffer = NULL; 119 } 120 } 121 122 bool DNSServer::requestIncludesOnlyOneQuestion() 123 { 124 return ntohs(_dnsHeader->QDCount) == 1 && 125 _dnsHeader->ANCount == 0 && 126 _dnsHeader->NSCount == 0 && 127 _dnsHeader->ARCount == 0; 128 } 129 130 131 String DNSServer::getDomainNameWithoutWwwPrefix() 132 { 133 // Error checking : if the buffer containing the DNS request is a null pointer, return an empty domain 134 String parsedDomainName = ""; 135 if (_buffer == NULL) 136 return parsedDomainName; 137 138 // Set the start of the domain just after the header (12 bytes). If equal to null character, return an empty domain 139 unsigned char *start = _buffer + DNS_OFFSET_DOMAIN_NAME; 140 if (*start == 0) 141 { 142 return parsedDomainName; 143 } 144 145 int pos = 0; 146 while(true) 147 { 148 unsigned char labelLength = *(start + pos); 149 for(int i = 0; i < labelLength; i++) 150 { 151 pos++; 152 parsedDomainName += (char)*(start + pos); 153 } 154 pos++; 155 if (*(start + pos) == 0) 156 { 157 downcaseAndRemoveWwwPrefix(parsedDomainName); 158 return parsedDomainName; 159 } 160 else 161 { 162 parsedDomainName += "."; 163 } 164 } 165 } 166 167 void DNSServer::replyWithIP() 168 { 169 if (_buffer == NULL) return; 170 171 _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); 172 173 // Change the type of message to a response and set the number of answers equal to 174 // the number of questions in the header 175 _dnsHeader->QR = DNS_QR_RESPONSE; 176 _dnsHeader->ANCount = _dnsHeader->QDCount; 177 _udp.write( (unsigned char*) _dnsHeader, DNS_HEADER_SIZE ) ; 178 179 // Write the question 180 _udp.write(_dnsQuestion->QName, _dnsQuestion->QNameLength) ; 181 _udp.write( (unsigned char*) &_dnsQuestion->QType, 2 ) ; 182 _udp.write( (unsigned char*) &_dnsQuestion->QClass, 2 ) ; 183 184 // Write the answer 185 // Use DNS name compression : instead of repeating the name in this RNAME occurence, 186 // set the two MSB of the byte corresponding normally to the length to 1. The following 187 // 14 bits must be used to specify the offset of the domain name in the message 188 // (<255 here so the first byte has the 6 LSB at 0) 189 _udp.write((uint8_t) 0xC0); 190 _udp.write((uint8_t) DNS_OFFSET_DOMAIN_NAME); 191 192 // DNS type A : host address, DNS class IN for INternet, returning an IPv4 address 193 uint16_t answerType = htons(DNS_TYPE_A), answerClass = htons(DNS_CLASS_IN), answerIPv4 = htons(DNS_RDLENGTH_IPV4) ; 194 _udp.write((unsigned char*) &answerType, 2 ); 195 _udp.write((unsigned char*) &answerClass, 2 ); 196 _udp.write((unsigned char*) &_ttl, 4); // DNS Time To Live 197 _udp.write((unsigned char*) &answerIPv4, 2 ); 198 _udp.write(_resolvedIP, sizeof(_resolvedIP)); // The IP address to return 199 _udp.endPacket(); 200 201 #ifdef DEBUG_ESP_DNS 202 DBG_OUTPUT_PORT.printf("DNS responds: %s for %s\n", 203 IPAddress(_resolvedIP).toString().c_str(), getDomainNameWithoutWwwPrefix().c_str() ); 204 #endif 205 } 206 207 void DNSServer::replyWithCustomCode() 208 { 209 if (_buffer == NULL) return; 210 _dnsHeader->QR = DNS_QR_RESPONSE; 211 _dnsHeader->RCode = (unsigned char)_errorReplyCode; 212 _dnsHeader->QDCount = 0; 213 214 _udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); 215 _udp.write(_buffer, sizeof(DNSHeader)); 216 _udp.endPacket(); 217 }