mirror of
https://github.com/rsyslog/rsyslog.git
synced 2025-12-20 00:20:42 +01:00
previously, hostname and ip strings were shuffled to the msg object, which created a property out of them. Now the cache holds the property, and it is resused (almost) everywhere, what saves a lot of memory operations. The only exception is imtcp session setup, where different handling of the hostname is done, which we need to sort out (but that's another story).
466 lines
14 KiB
C
466 lines
14 KiB
C
/* dnscache.c
|
|
* Implementation of a real DNS cache
|
|
*
|
|
* File begun on 2011-06-06 by RGerhards
|
|
* The initial implementation is far from being optimal. The idea is to
|
|
* first get somethting that'S functionally OK, and then evolve the algorithm.
|
|
* In any case, even the initial implementaton is far faster than what we had
|
|
* before. -- rgerhards, 2011-06-06
|
|
*
|
|
* Copyright 2011-2013 by Rainer Gerhards and Adiscon GmbH.
|
|
*
|
|
* This file is part of the rsyslog runtime library.
|
|
*
|
|
* Licensed 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
|
|
* -or-
|
|
* see COPYING.ASL20 in the source distribution
|
|
*
|
|
* 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.
|
|
*/
|
|
#include "config.h"
|
|
|
|
#include "rsyslog.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include "syslogd-types.h"
|
|
#include "glbl.h"
|
|
#include "errmsg.h"
|
|
#include "obj.h"
|
|
#include "unicode-helper.h"
|
|
#include "net.h"
|
|
#include "hashtable.h"
|
|
#include "prop.h"
|
|
#include "dnscache.h"
|
|
|
|
/* module data structures */
|
|
struct dnscache_entry_s {
|
|
struct sockaddr_storage addr;
|
|
prop_t *fqdn;
|
|
prop_t *fqdnLowerCase;
|
|
prop_t *localName; /* only local name, without domain part (if configured so) */
|
|
prop_t *ip;
|
|
struct dnscache_entry_s *next;
|
|
unsigned nUsed;
|
|
};
|
|
typedef struct dnscache_entry_s dnscache_entry_t;
|
|
struct dnscache_s {
|
|
pthread_rwlock_t rwlock;
|
|
struct hashtable *ht;
|
|
unsigned nEntries;
|
|
};
|
|
typedef struct dnscache_s dnscache_t;
|
|
|
|
|
|
/* static data */
|
|
DEFobjStaticHelpers
|
|
DEFobjCurrIf(glbl)
|
|
DEFobjCurrIf(errmsg)
|
|
DEFobjCurrIf(prop)
|
|
static dnscache_t dnsCache;
|
|
static prop_t *staticErrValue;
|
|
|
|
|
|
/* Our hash function.
|
|
* TODO: check how well it performs on socket addresses!
|
|
*/
|
|
unsigned int
|
|
hash_from_key_fn(void *k)
|
|
{
|
|
int len;
|
|
uchar *rkey = (uchar*) k; /* we treat this as opaque bytes */
|
|
unsigned hashval = 1;
|
|
|
|
len = SALEN((struct sockaddr*)k);
|
|
while(len--)
|
|
hashval = hashval * 33 + *rkey++;
|
|
|
|
return hashval;
|
|
}
|
|
|
|
static int
|
|
key_equals_fn(void *key1, void *key2)
|
|
{
|
|
return (SALEN((struct sockaddr*)key1) == SALEN((struct sockaddr*) key2)
|
|
&& !memcmp(key1, key2, SALEN((struct sockaddr*) key1)));
|
|
}
|
|
|
|
/* destruct a cache entry.
|
|
* Precondition: entry must already be unlinked from list
|
|
*/
|
|
static void
|
|
entryDestruct(dnscache_entry_t *etry)
|
|
{
|
|
if(etry->fqdn != NULL)
|
|
prop.Destruct(&etry->fqdn);
|
|
if(etry->fqdnLowerCase != NULL)
|
|
prop.Destruct(&etry->fqdnLowerCase);
|
|
if(etry->localName != NULL)
|
|
prop.Destruct(&etry->localName);
|
|
if(etry->ip != NULL)
|
|
prop.Destruct(&etry->ip);
|
|
free(etry);
|
|
}
|
|
|
|
/* init function (must be called once) */
|
|
rsRetVal
|
|
dnscacheInit(void)
|
|
{
|
|
DEFiRet;
|
|
if((dnsCache.ht = create_hashtable(100, hash_from_key_fn, key_equals_fn,
|
|
(void(*)(void*))entryDestruct)) == NULL) {
|
|
DBGPRINTF("dnscache: error creating hash table!\n");
|
|
ABORT_FINALIZE(RS_RET_ERR); // TODO: make this degrade, but run!
|
|
}
|
|
dnsCache.nEntries = 0;
|
|
pthread_rwlock_init(&dnsCache.rwlock, NULL);
|
|
CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */
|
|
CHKiRet(objUse(glbl, CORE_COMPONENT));
|
|
CHKiRet(objUse(errmsg, CORE_COMPONENT));
|
|
CHKiRet(objUse(prop, CORE_COMPONENT));
|
|
|
|
prop.Construct(&staticErrValue);
|
|
prop.SetString(staticErrValue, (uchar*)"???", 3);
|
|
prop.ConstructFinalize(staticErrValue);
|
|
finalize_it:
|
|
RETiRet;
|
|
}
|
|
|
|
/* deinit function (must be called once) */
|
|
rsRetVal
|
|
dnscacheDeinit(void)
|
|
{
|
|
DEFiRet;
|
|
prop.Destruct(&staticErrValue);
|
|
hashtable_destroy(dnsCache.ht, 1); /* 1 => free all values automatically */
|
|
pthread_rwlock_destroy(&dnsCache.rwlock);
|
|
objRelease(glbl, CORE_COMPONENT);
|
|
objRelease(errmsg, CORE_COMPONENT);
|
|
objRelease(prop, CORE_COMPONENT);
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
static inline dnscache_entry_t*
|
|
findEntry(struct sockaddr_storage *addr)
|
|
{
|
|
return((dnscache_entry_t*) hashtable_search(dnsCache.ht, addr));
|
|
}
|
|
|
|
|
|
/* This is a cancel-safe getnameinfo() version, because we learned
|
|
* (via drd/valgrind) that getnameinfo() seems to have some issues
|
|
* when being cancelled, at least if the module was dlloaded.
|
|
* rgerhards, 2008-09-30
|
|
*/
|
|
static inline int
|
|
mygetnameinfo(const struct sockaddr *sa, socklen_t salen,
|
|
char *host, size_t hostlen,
|
|
char *serv, size_t servlen, int flags)
|
|
{
|
|
int iCancelStateSave;
|
|
int i;
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
|
|
i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
|
|
pthread_setcancelstate(iCancelStateSave, NULL);
|
|
return i;
|
|
}
|
|
|
|
|
|
/* get only the local part of the hostname and set it in cache entry */
|
|
static inline void
|
|
setLocalHostName(dnscache_entry_t *etry)
|
|
{
|
|
uchar *fqdnLower;
|
|
uchar *p;
|
|
int count;
|
|
int i;
|
|
uchar hostbuf[NI_MAXHOST];
|
|
|
|
if(glbl.GetPreserveFQDN()) {
|
|
prop.AddRef(etry->fqdnLowerCase);
|
|
etry->localName = etry->fqdnLowerCase;
|
|
goto done;
|
|
}
|
|
|
|
/* strip domain, if configured for this entry */
|
|
fqdnLower = propGetSzStr(etry->fqdnLowerCase);
|
|
p = (uchar*)strchr((char*)fqdnLower, '.'); /* find start of domain name "machine.example.com" */
|
|
if(p == NULL) { /* do we have a domain part? */
|
|
prop.AddRef(etry->fqdnLowerCase); /* no! */
|
|
etry->localName = etry->fqdnLowerCase;
|
|
goto done;
|
|
}
|
|
|
|
i = p - fqdnLower; /* length of hostname */
|
|
memcpy(hostbuf, fqdnLower, i);
|
|
/* now check if we belong to any of the domain names that were specified
|
|
* in the -s command line option. If so, remove and we are done.
|
|
*/
|
|
if(glbl.GetStripDomains() != NULL) {
|
|
count=0;
|
|
while(glbl.GetStripDomains()[count]) {
|
|
if(strcmp((char*)(p + 1), glbl.GetStripDomains()[count]) == 0) {
|
|
prop.CreateStringProp(&etry->localName, hostbuf, i);
|
|
goto done;
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
/* if we reach this point, we have not found any domain we should strip. Now
|
|
* we try and see if the host itself is listed in the -l command line option
|
|
* and so should be stripped also. If so, we do it and return. Please note that
|
|
* -l list FQDNs, not just the hostname part. If it did just list the hostname, the
|
|
* door would be wide-open for all kinds of mixing up of hosts. Because of this,
|
|
* you'll see comparison against the full string (pszHostFQDN) below.
|
|
*/
|
|
if(glbl.GetLocalHosts() != NULL) {
|
|
count=0;
|
|
while(glbl.GetLocalHosts()[count]) {
|
|
if(!strcmp((char*)fqdnLower, (char*)glbl.GetLocalHosts()[count])) {
|
|
prop.CreateStringProp(&etry->localName, hostbuf, i);
|
|
goto done;
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/* at this point, we have not found anything, so we again use the
|
|
* already-created complete full name property.
|
|
*/
|
|
prop.AddRef(etry->fqdnLowerCase);
|
|
etry->localName = etry->fqdnLowerCase;
|
|
done: return;
|
|
}
|
|
|
|
|
|
/* resolve an address.
|
|
*
|
|
* Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
|
|
* for some explanation of the code found below. We do by default not
|
|
* discard message where we detected malicouos DNS PTR records. However,
|
|
* there is a user-configurabel option that will tell us if
|
|
* we should abort. For this, the return value tells the caller if the
|
|
* message should be processed (1) or discarded (0).
|
|
*/
|
|
static rsRetVal
|
|
resolveAddr(struct sockaddr_storage *addr, dnscache_entry_t *etry)
|
|
{
|
|
DEFiRet;
|
|
int error;
|
|
sigset_t omask, nmask;
|
|
struct addrinfo hints, *res;
|
|
char szIP[80]; /* large enough for IPv6 */
|
|
char fqdnBuf[NI_MAXHOST];
|
|
rs_size_t fqdnLen;
|
|
rs_size_t i;
|
|
|
|
error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *)addr),
|
|
(char*) szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST);
|
|
if(error) {
|
|
dbgprintf("Malformed from address %s\n", gai_strerror(error));
|
|
ABORT_FINALIZE(RS_RET_INVALID_SOURCE);
|
|
}
|
|
|
|
if(!glbl.GetDisableDNS()) {
|
|
sigemptyset(&nmask);
|
|
sigaddset(&nmask, SIGHUP);
|
|
pthread_sigmask(SIG_BLOCK, &nmask, &omask);
|
|
|
|
error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *) addr),
|
|
fqdnBuf, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
|
|
|
|
if(error == 0) {
|
|
memset (&hints, 0, sizeof (struct addrinfo));
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
|
|
/* we now do a lookup once again. This one should fail,
|
|
* because we should not have obtained a non-numeric address. If
|
|
* we got a numeric one, someone messed with DNS!
|
|
*/
|
|
if(getaddrinfo (fqdnBuf, NULL, &hints, &res) == 0) {
|
|
uchar szErrMsg[1024];
|
|
freeaddrinfo (res);
|
|
/* OK, we know we have evil. The question now is what to do about
|
|
* it. One the one hand, the message might probably be intended
|
|
* to harm us. On the other hand, losing the message may also harm us.
|
|
* Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords
|
|
* option. If it tells us we should discard, we do so, else we proceed,
|
|
* but log an error message together with it.
|
|
* time being, we simply drop the name we obtained and use the IP - that one
|
|
* is OK in any way. We do also log the error message. rgerhards, 2007-07-16
|
|
*/
|
|
if(glbl.GetDropMalPTRMsgs() == 1) {
|
|
snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
|
|
"Malicious PTR record, message dropped "
|
|
"IP = \"%s\" HOST = \"%s\"",
|
|
szIP, fqdnBuf);
|
|
errmsg.LogError(0, RS_RET_MALICIOUS_ENTITY, "%s", szErrMsg);
|
|
pthread_sigmask(SIG_SETMASK, &omask, NULL);
|
|
ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY);
|
|
}
|
|
|
|
/* Please note: we deal with a malicous entry. Thus, we have crafted
|
|
* the snprintf() below so that all text is in front of the entry - maybe
|
|
* it contains characters that make the message unreadable
|
|
* (OK, I admit this is more or less impossible, but I am paranoid...)
|
|
* rgerhards, 2007-07-16
|
|
*/
|
|
snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
|
|
"Malicious PTR record (message accepted, but used IP "
|
|
"instead of PTR name: IP = \"%s\" HOST = \"%s\"",
|
|
szIP, fqdnBuf);
|
|
errmsg.LogError(0, NO_ERRCODE, "%s", szErrMsg);
|
|
|
|
error = 1; /* that will trigger using IP address below. */
|
|
} else {/* we have a valid entry, so let's create the respective properties */
|
|
fqdnLen = strlen(fqdnBuf);
|
|
prop.CreateStringProp(&etry->fqdn, (uchar*)fqdnBuf, fqdnLen);
|
|
for(i = 0 ; i < fqdnLen ; ++i)
|
|
fqdnBuf[i] = tolower(fqdnBuf[i]);
|
|
prop.CreateStringProp(&etry->fqdnLowerCase, (uchar*)fqdnBuf, fqdnLen);
|
|
}
|
|
}
|
|
pthread_sigmask(SIG_SETMASK, &omask, NULL);
|
|
}
|
|
|
|
|
|
finalize_it:
|
|
if(iRet != RS_RET_OK) {
|
|
strcpy(szIP, "?error.obtaining.ip?");
|
|
error = 1; /* trigger hostname copies below! */
|
|
}
|
|
|
|
/* we need to create the inputName property (only once during our lifetime) */
|
|
prop.CreateStringProp(&etry->ip, (uchar*)szIP, strlen(szIP));
|
|
|
|
if(error || glbl.GetDisableDNS()) {
|
|
dbgprintf("Host name for your address (%s) unknown\n", szIP);
|
|
prop.AddRef(etry->ip);
|
|
etry->fqdn = etry->ip;
|
|
prop.AddRef(etry->ip);
|
|
etry->fqdnLowerCase = etry->ip;
|
|
}
|
|
|
|
setLocalHostName(etry);
|
|
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
static inline rsRetVal
|
|
addEntry(struct sockaddr_storage *addr, dnscache_entry_t **pEtry)
|
|
{
|
|
int r;
|
|
struct sockaddr_storage *keybuf;
|
|
dnscache_entry_t *etry = NULL;
|
|
DEFiRet;
|
|
|
|
CHKmalloc(etry = MALLOC(sizeof(dnscache_entry_t)));
|
|
CHKiRet(resolveAddr(addr, etry));
|
|
memcpy(&etry->addr, addr, SALEN((struct sockaddr*) addr));
|
|
etry->nUsed = 0;
|
|
*pEtry = etry;
|
|
|
|
CHKmalloc(keybuf = malloc(sizeof(struct sockaddr_storage)));
|
|
memcpy(keybuf, addr, sizeof(struct sockaddr_storage));
|
|
|
|
pthread_rwlock_unlock(&dnsCache.rwlock); /* release read lock */
|
|
pthread_rwlock_wrlock(&dnsCache.rwlock); /* and re-aquire for writing */
|
|
r = hashtable_insert(dnsCache.ht, keybuf, *pEtry);
|
|
if(r == 0) {
|
|
DBGPRINTF("dnscache: inserting element failed\n");
|
|
}
|
|
pthread_rwlock_unlock(&dnsCache.rwlock);
|
|
pthread_rwlock_rdlock(&dnsCache.rwlock); /* we need this again */
|
|
|
|
finalize_it:
|
|
if(iRet != RS_RET_OK && etry != NULL) {
|
|
/* Note: sub-fields cannot be populated in this case */
|
|
free(etry);
|
|
}
|
|
RETiRet;
|
|
}
|
|
|
|
|
|
/* validate if an entry is still valid and, if not, re-query it.
|
|
* In the initial implementation, this is a dummy!
|
|
* TODO: implement!
|
|
*/
|
|
static inline rsRetVal
|
|
validateEntry(dnscache_entry_t __attribute__((unused)) *etry, struct sockaddr_storage __attribute__((unused)) *addr)
|
|
{
|
|
return RS_RET_OK;
|
|
}
|
|
|
|
|
|
/* This is the main function: it looks up an entry and returns it's name
|
|
* and IP address. If the entry is not yet inside the cache, it is added.
|
|
* If the entry can not be resolved, an error is reported back. If fqdn
|
|
* or fqdnLowerCase are NULL, they are not set.
|
|
*/
|
|
rsRetVal
|
|
dnscacheLookup(struct sockaddr_storage *addr, prop_t **fqdn, prop_t **fqdnLowerCase,
|
|
prop_t **localName, prop_t **ip)
|
|
{
|
|
dnscache_entry_t *etry;
|
|
DEFiRet;
|
|
|
|
pthread_rwlock_rdlock(&dnsCache.rwlock); /* TODO: optimize this! */
|
|
etry = findEntry(addr);
|
|
dbgprintf("dnscache: entry %p found\n", etry);
|
|
if(etry == NULL) {
|
|
CHKiRet(addEntry(addr, &etry));
|
|
} else {
|
|
CHKiRet(validateEntry(etry, addr));
|
|
}
|
|
prop.AddRef(etry->ip);
|
|
*ip = etry->ip;
|
|
if(fqdn != NULL) {
|
|
prop.AddRef(etry->fqdn);
|
|
*fqdn = etry->fqdn;
|
|
}
|
|
if(fqdnLowerCase != NULL) {
|
|
prop.AddRef(etry->fqdnLowerCase);
|
|
*fqdnLowerCase = etry->fqdnLowerCase;
|
|
}
|
|
if(localName != NULL) {
|
|
prop.AddRef(etry->localName);
|
|
*localName = etry->localName;
|
|
}
|
|
|
|
finalize_it:
|
|
pthread_rwlock_unlock(&dnsCache.rwlock);
|
|
if(iRet != RS_RET_OK && iRet != RS_RET_ADDRESS_UNKNOWN) {
|
|
DBGPRINTF("dnscacheLookup failed with iRet %d\n", iRet);
|
|
prop.AddRef(staticErrValue);
|
|
*ip = staticErrValue;
|
|
if(fqdn != NULL) {
|
|
prop.AddRef(staticErrValue);
|
|
*fqdn = staticErrValue;
|
|
}
|
|
if(fqdnLowerCase != NULL) {
|
|
prop.AddRef(staticErrValue);
|
|
*fqdnLowerCase = staticErrValue;
|
|
}
|
|
if(localName != NULL) {
|
|
prop.AddRef(staticErrValue);
|
|
*localName = staticErrValue;
|
|
}
|
|
}
|
|
RETiRet;
|
|
}
|