| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| /***************************************************************************** |
| * |
| * SplitDNS.cc - Implementation of "split" DNS (as the name says) |
| * |
| * |
| ****************************************************************************/ |
| |
| #include "tscore/ink_platform.h" |
| #include "tscore/Tokenizer.h" |
| #include "tscore/Filenames.h" |
| |
| #include <sys/types.h> |
| #include "P_SplitDNS.h" |
| #include "tscore/MatcherUtils.h" |
| #include "tscore/HostLookup.h" |
| |
| /* -------------------------------------------------------------- |
| this file is built using "ParentSelection.cc as a template. |
| ------------- ------------------------------------------------- */ |
| |
| /* -------------------------------------------------------------- |
| globals |
| -------------------------------------------------------------- */ |
| static const char modulePrefix[] = "[SplitDNS]"; |
| |
| ConfigUpdateHandler<SplitDNSConfig> *SplitDNSConfig::splitDNSUpdate = nullptr; |
| |
| static ClassAllocator<DNSRequestData> DNSReqAllocator("DNSRequestDataAllocator"); |
| |
| /* -------------------------------------------------------------- |
| used by a lot of protocols. We do not have dest ip in most |
| cases. |
| -------------------------------------------------------------- */ |
| const matcher_tags sdns_dest_tags = {"dest_host", "dest_domain", nullptr, "url_regex", "url", nullptr, true}; |
| |
| /* -------------------------------------------------------------- |
| config Callback Prototypes |
| -------------------------------------------------------------- */ |
| enum SplitDNSCB_t { |
| SDNS_FILE_CB, |
| SDNS_ENABLE_CB, |
| }; |
| |
| static const char *SDNSResultStr[] = {"DNSServer_Undefined", "DNSServer_Specified", "DNSServer_Failed"}; |
| |
| int SplitDNSConfig::m_id = 0; |
| int SplitDNSConfig::gsplit_dns_enabled = 0; |
| int splitDNSFile_CB(const char *name, RecDataT data_type, RecData data, void *cookie); |
| Ptr<ProxyMutex> SplitDNSConfig::dnsHandler_mutex; |
| |
| /* -------------------------------------------------------------- |
| SplitDNSResult::SplitDNSResult() |
| -------------------------------------------------------------- */ |
| inline SplitDNSResult::SplitDNSResult() {} |
| |
| /* -------------------------------------------------------------- |
| SplitDNS::SplitDNS() |
| -------------------------------------------------------------- */ |
| SplitDNS::SplitDNS() {} |
| |
| SplitDNS::~SplitDNS() |
| { |
| if (m_DNSSrvrTable) { |
| delete m_DNSSrvrTable; |
| } |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSConfig::acquire() |
| -------------------------------------------------------------- */ |
| SplitDNS * |
| SplitDNSConfig::acquire() |
| { |
| return static_cast<SplitDNS *>(configProcessor.get(SplitDNSConfig::m_id)); |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSConfig::release() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNSConfig::release(SplitDNS *params) |
| { |
| configProcessor.release(SplitDNSConfig::m_id, params); |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSConfig::startup() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNSConfig::startup() |
| { |
| dnsHandler_mutex = new_ProxyMutex(); |
| |
| // startup just check gsplit_dns_enabled |
| REC_ReadConfigInt32(gsplit_dns_enabled, "proxy.config.dns.splitDNS.enabled"); |
| SplitDNSConfig::splitDNSUpdate = new ConfigUpdateHandler<SplitDNSConfig>(); |
| SplitDNSConfig::splitDNSUpdate->attach("proxy.config.cache.splitdns.filename"); |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSConfig::reconfigure() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNSConfig::reconfigure() |
| { |
| if (0 == gsplit_dns_enabled) { |
| return; |
| } |
| |
| Note("%s loading ...", ts::filename::SPLITDNS); |
| |
| SplitDNS *params = new SplitDNS; |
| |
| params->m_SplitDNSlEnable = gsplit_dns_enabled; |
| params->m_DNSSrvrTable = new DNS_table("proxy.config.dns.splitdns.filename", modulePrefix, &sdns_dest_tags); |
| |
| if (nullptr == params->m_DNSSrvrTable || (0 == params->m_DNSSrvrTable->getEntryCount())) { |
| Warning("Failed to load %s - No NAMEDs provided! Disabling SplitDNS", ts::filename::SPLITDNS); |
| gsplit_dns_enabled = 0; |
| delete params; |
| return; |
| } |
| params->m_numEle = params->m_DNSSrvrTable->getEntryCount(); |
| |
| if (nullptr != params->m_DNSSrvrTable->getHostMatcher() && nullptr == params->m_DNSSrvrTable->getReMatcher() && |
| nullptr == params->m_DNSSrvrTable->getIPMatcher() && 4 >= params->m_numEle) { |
| HostLookup *pxHL = params->m_DNSSrvrTable->getHostMatcher()->getHLookup(); |
| params->m_pxLeafArray = pxHL->get_leaf_array(); |
| params->m_bEnableFastPath = true; |
| } |
| |
| m_id = configProcessor.set(m_id, params); |
| |
| if (is_debug_tag_set("splitdns_config")) { |
| SplitDNSConfig::print(); |
| } |
| |
| Note("%s finished loading", ts::filename::SPLITDNS); |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSConfig::print() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNSConfig::print() |
| { |
| SplitDNS *params = SplitDNSConfig::acquire(); |
| |
| Debug("splitdns_config", "DNS Server Selection Config"); |
| Debug("splitdns_config", "\tEnabled=%d", params->m_SplitDNSlEnable); |
| |
| params->m_DNSSrvrTable->Print(); |
| SplitDNSConfig::release(params); |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNS::getDNSRecord() |
| -------------------------------------------------------------- */ |
| void * |
| SplitDNS::getDNSRecord(const char *hostname) |
| { |
| Debug("splitdns", "Called SplitDNS::getDNSRecord(%s)", hostname); |
| |
| DNSRequestData *pRD = DNSReqAllocator.alloc(); |
| pRD->m_pHost = hostname; |
| |
| SplitDNSResult res; |
| findServer(pRD, &res); |
| |
| DNSReqAllocator.free(pRD); |
| |
| if (DNS_SRVR_SPECIFIED == res.r) { |
| return (void *)&(res.m_rec->m_servers); |
| } |
| |
| Debug("splitdns", "Fail to match a valid splitdns rule, fallback to default dns resolver"); |
| return nullptr; |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNS::findServer() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNS::findServer(RequestData *rdata, SplitDNSResult *result) |
| { |
| DNS_table *tablePtr = m_DNSSrvrTable; |
| SplitDNSRecord *rec; |
| |
| ink_assert(result->r == DNS_SRVR_UNDEFINED); |
| |
| if (m_SplitDNSlEnable == 0) { |
| result->r = DNS_SRVR_UNDEFINED; |
| return; |
| } |
| |
| result->m_rec = nullptr; |
| result->m_line_number = 0xffffffff; |
| result->m_wrap_around = false; |
| |
| /* --------------------------- |
| the 'alleged' fast path ... |
| --------------------------- */ |
| if (m_bEnableFastPath) { |
| SplitDNSRecord *data_ptr = nullptr; |
| char *pHost = const_cast<char *>(rdata->get_host()); |
| if (nullptr == pHost) { |
| Warning("SplitDNS: No host to match !"); |
| return; |
| } |
| |
| int len = strlen(pHost); |
| HostLeaf *pxHL = static_cast<HostLeaf *>(m_pxLeafArray); |
| for (int i = 0; i < m_numEle; i++) { |
| if (nullptr == pxHL) { |
| break; |
| } |
| |
| if (false == pxHL[i].isNot && static_cast<int>(pxHL[i].match.size()) > len) { |
| continue; |
| } |
| |
| int idx = len - pxHL[i].match.size(); |
| char *pH = &pHost[idx]; |
| const char *pMatch = pxHL[i].match.data(); |
| char cNot = *pMatch; |
| |
| if ('!' == cNot) { |
| pMatch++; |
| } |
| |
| int res = memcmp(pH, pMatch, pxHL[i].match.size()); |
| |
| if ((0 != res && '!' == cNot) || (0 == res && '!' != cNot)) { |
| data_ptr = static_cast<SplitDNSRecord *>(pxHL[i].opaque_data); |
| data_ptr->UpdateMatch(result, rdata); |
| break; |
| } |
| } |
| } else { |
| tablePtr->Match(rdata, result); |
| } |
| |
| rec = result->m_rec; |
| if (rec == nullptr) { |
| result->r = DNS_SRVR_UNDEFINED; |
| return; |
| } else { |
| result->r = DNS_SRVR_SPECIFIED; |
| } |
| |
| if (is_debug_tag_set("splitdns_config")) { |
| const char *host = rdata->get_host(); |
| |
| switch (result->r) { |
| case DNS_SRVR_FAIL: |
| Debug("splitdns_config", "Result for %s was %s", host, SDNSResultStr[result->r]); |
| break; |
| case DNS_SRVR_SPECIFIED: |
| Debug("splitdns_config", "Result for %s was dns servers", host); |
| result->m_rec->Print(); |
| break; |
| default: |
| // DNS_SRVR_UNDEFINED |
| break; |
| } |
| } |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSRecord::ProcessDNSHosts() |
| -------------------------------------------------------------- */ |
| const char * |
| SplitDNSRecord::ProcessDNSHosts(char *val) |
| { |
| Tokenizer pTok(",; \t\r"); |
| int numTok; |
| int port = 0; |
| int totsz = 0, sz = 0; |
| |
| numTok = pTok.Initialize(val, SHARE_TOKS); |
| if (MAXNS < numTok) { |
| numTok = MAXNS; |
| Warning("Only first %d DNS servers are tracked", numTok); |
| } |
| if (numTok == 0) { |
| return "No servers specified"; |
| } |
| |
| /* ------------------------------------------------ |
| Allocate the servers array and Loop through the |
| set of servers specified |
| ------------------------------------------------ */ |
| for (int i = 0; i < numTok; i++) { |
| const char *current = pTok[i]; |
| char *tmp = const_cast<char *>(strchr(current, ':')); |
| // coverity[secure_coding] |
| if (tmp != nullptr && sscanf(tmp + 1, "%d", &port) != 1) { |
| return "Malformed DNS port"; |
| } |
| |
| /* ---------------------------------------- |
| Make sure that is no garbage beyond the |
| server port |
| ---------------------------------------- */ |
| if (tmp) { |
| char *scan = tmp + 1; |
| for (; *scan != '\0' && ParseRules::is_digit(*scan); scan++) { |
| ; |
| } |
| for (; *scan != '\0' && ParseRules::is_wslfcr(*scan); scan++) { |
| ; |
| } |
| |
| if (*scan != '\0') { |
| return "Garbage trailing entry or invalid separator"; |
| } |
| |
| if (tmp - current > (MAXDNAME - 1)) { |
| return "DNS server name (ip) is too long"; |
| } else if (tmp - current == 0) { |
| return "server string is empty"; |
| } |
| *tmp = 0; |
| } |
| |
| if (0 != ats_ip_pton(current, &m_servers.x_server_ip[i].sa)) { |
| return "invalid IP address given for a DNS server"; |
| } |
| |
| ats_ip_port_cast(&m_servers.x_server_ip[i].sa) = htons(port ? port : NAMESERVER_PORT); |
| |
| if ((MAXDNAME * 2 - 1) > totsz) { |
| sz = strlen(current); |
| memcpy((m_servers.x_dns_ip_line + totsz), current, sz); |
| totsz += sz; |
| } |
| } |
| |
| m_dnsSrvr_cnt = numTok; |
| return nullptr; |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSRecord::ProcessDefDomain() |
| -------------------------------------------------------------- */ |
| const char * |
| SplitDNSRecord::ProcessDefDomain(char *val) |
| { |
| Tokenizer pTok(",; \t\r"); |
| int numTok; |
| |
| numTok = pTok.Initialize(val, SHARE_TOKS); |
| |
| if (numTok > 1) { |
| return "more than one default domain name specified"; |
| } |
| |
| if (numTok == 0) { |
| return "no default domain name specified"; |
| } |
| |
| int len = 0; |
| if (pTok[0] && 0 != (len = strlen(pTok[0]))) { |
| memcpy(&m_servers.x_def_domain[0], pTok[0], len); |
| m_servers.x_def_domain[len] = '\0'; |
| } |
| |
| return nullptr; |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSRecord::ProcessDomainSrchList() |
| -------------------------------------------------------------- */ |
| const char * |
| SplitDNSRecord::ProcessDomainSrchList(char *val) |
| { |
| Tokenizer pTok(",; \t\r"); |
| int numTok; |
| int sz = 0; |
| char *pSp = nullptr; |
| |
| numTok = pTok.Initialize(val, SHARE_TOKS); |
| |
| if (numTok == 0) { |
| return "No servers specified"; |
| } |
| |
| pSp = &m_servers.x_domain_srch_list[0]; |
| |
| for (int i = 0; i < numTok; i++) { |
| const char *current = pTok[i]; |
| int cnt = sz += strlen(current); |
| |
| if (MAXDNAME - 1 < sz) { |
| break; |
| } |
| |
| memcpy(pSp, current, cnt); |
| pSp += (cnt + 1); |
| } |
| |
| m_domain_srch_list = numTok; |
| return nullptr; |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSRecord::Init() |
| |
| matcher_line* line_info - contains parsed label/value pairs |
| of the current split.config line |
| -------------------------------------------------------------- */ |
| Result |
| SplitDNSRecord::Init(matcher_line *line_info) |
| { |
| const char *errPtr = nullptr; |
| |
| this->line_num = line_info->line_num; |
| for (int i = 0; i < MATCHER_MAX_TOKENS; i++) { |
| char *label = line_info->line[0][i]; |
| char *val = line_info->line[1][i]; |
| |
| if (label == nullptr) { |
| continue; |
| } |
| |
| if (strcasecmp(label, "def_domain") == 0) { |
| if (nullptr != (errPtr = ProcessDefDomain(val))) { |
| return Result::failure("%s %s at line %d", modulePrefix, errPtr, line_num); |
| } |
| line_info->line[0][i] = nullptr; |
| line_info->num_el--; |
| continue; |
| } |
| |
| if (strcasecmp(label, "search_list") == 0) { |
| if (nullptr != (errPtr = ProcessDomainSrchList(val))) { |
| return Result::failure("%s %s at line %d", modulePrefix, errPtr, line_num); |
| } |
| line_info->line[0][i] = nullptr; |
| line_info->num_el--; |
| continue; |
| } |
| |
| if (strcasecmp(label, "named") == 0) { |
| if (nullptr != (errPtr = ProcessDNSHosts(val))) { |
| return Result::failure("%s %s at line %d", modulePrefix, errPtr, line_num); |
| } |
| line_info->line[0][i] = nullptr; |
| line_info->num_el--; |
| continue; |
| } |
| } |
| |
| if (!ats_is_ip(&m_servers.x_server_ip[0].sa)) { |
| return Result::failure("%s No server specified in %s at line %d", modulePrefix, ts::filename::SPLITDNS, line_num); |
| } |
| |
| DNSHandler *dnsH = new DNSHandler; |
| ink_res_state res = new ts_imp_res_state; |
| |
| memset(res, 0, sizeof(ts_imp_res_state)); |
| if ((-1 == ink_res_init(res, m_servers.x_server_ip, m_dnsSrvr_cnt, dns_search, m_servers.x_def_domain, |
| m_servers.x_domain_srch_list, nullptr))) { |
| char ab[INET6_ADDRPORTSTRLEN]; |
| return Result::failure("Failed to build res record for the servers %s ...", |
| ats_ip_ntop(&m_servers.x_server_ip[0].sa, ab, sizeof ab)); |
| } |
| |
| dnsH->m_res = res; |
| dnsH->mutex = SplitDNSConfig::dnsHandler_mutex; |
| ats_ip_invalidate(&dnsH->ip.sa); // Mark to use default DNS. |
| |
| m_servers.x_dnsH = dnsH; |
| |
| SET_CONTINUATION_HANDLER(dnsH, &DNSHandler::startEvent_sdns); |
| eventProcessor.thread_group[ET_DNS]._thread[0]->schedule_imm(dnsH); |
| |
| /* ----------------------------------------------------- |
| Process any modifiers to the directive, if they exist |
| ----------------------------------------------------- */ |
| if (line_info->num_el > 0) { |
| const char *tmp = ProcessModifiers(line_info); |
| if (tmp != nullptr) { |
| return Result::failure("%s %s at line %d in %s", modulePrefix, tmp, line_num, ts::filename::SPLITDNS); |
| } |
| } |
| |
| return Result::ok(); |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSRecord::UpdateMatch() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNSRecord::UpdateMatch(SplitDNSResult *result, RequestData * /* rdata ATS_UNUSED */) |
| { |
| int last_number = result->m_line_number; |
| |
| if ((last_number < 0) || (last_number > this->line_num)) { |
| result->m_rec = this; |
| result->m_line_number = this->line_num; |
| |
| Debug("splitdns_config", "Matched with %p dns node from line %d", this, this->line_num); |
| } |
| } |
| |
| /* -------------------------------------------------------------- |
| SplitDNSRecord::Print() |
| -------------------------------------------------------------- */ |
| void |
| SplitDNSRecord::Print() const |
| { |
| for (int i = 0; i < m_dnsSrvr_cnt; i++) { |
| char ab[INET6_ADDRPORTSTRLEN]; |
| Debug("splitdns_config", " %s", ats_ip_ntop(&m_servers.x_server_ip[i].sa, ab, sizeof ab)); |
| } |
| } |
| |
| void |
| ink_split_dns_init(ts::ModuleVersion v) |
| { |
| static int init_called = 0; |
| |
| ink_release_assert(v.check(SPLITDNS_MODULE_INTERNAL_VERSION)); |
| if (init_called) { |
| return; |
| } |
| |
| init_called = 1; |
| } |