2
0
This repository has been archived on 2025-11-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

738 lines
18 KiB
C

/*
* This software is Copyright 2011 by Sean Groarke <sgroarke@gmail.com>
* All rights reserved.
*
* This file is part of npd6.
*
* npd6 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* npd6 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with npd6. If not, see <http://www.gnu.org/licenses/>.
*/
/* $Id$
* $HeadURL$
*/
#include "includes.h"
#include "npd6.h"
//*******************************************************
// When we receive sigusrN, do something awesome.
/*****************************************************************************
* usersignal
* When dispatcher picks up a user signal, do something with it.
*
* Inputs:
* int mysig
* The signal received.
*
* Outputs:
* Various, depending upon the sig.
*
* Return:
* void
*
*/
void usersignal(int mysig)
{
switch(mysig) {
case SIGUSR1:
signal(SIGUSR1, usersignal);
flog(LOG_DEBUG, "called with USR1");
flog(LOG_INFO, "SIGUSR1 received: rereading config");
if ( readConfig(configfile) )
{
flog(LOG_ERR, "Error in config file: %s", configfile);
exit(1);
}
break;
case SIGUSR2:
signal(SIGUSR2, usersignal);
flog(LOG_DEBUG, "called with USR2");
dumpAddressData();
break;
case SIGHUP:
signal(SIGUSR2, usersignal);
flog(LOG_DEBUG, "called with HUP");
/* action? */
break;
case SIGINT:
case SIGTERM:
signal(SIGUSR2, usersignal);
flog(LOG_DEBUG, "called with INT");
/* We're dying, so handle directly here*/
dropdead();
break;
default:
flog(LOG_DEBUG, "Why am I here? Confused.");
break;
}
}
/*****************************************************************************
* npd6log
* Log as per level to the previously defined log file or system..
*
* Inputs:
* char * fn name
* From the caller's __FUNCTION__
* int pri
* Log level: e.g. LOG_ERR, LOG_INFO, LOG_DEBUG, etc.
* char *format
* Standard format string.
* ...
* Variable number of params to accompany the format string.
* GLOBAL
* logFileFD
* Previously opened lof device.
*
* Outputs:
* Via fprintf to the log file.
*
* Return:
* 0 on success
*
* Notes:
* This will be called from the macro expansion of "flog()"
* This gets called a lot, but much of the time only does
* anything if the debug options are turned on. Hence it's
* written in a slightly odd manner to avoid data allocation
* or work unless we have debug turned on of it's a non-debug
* call.
*/
int npd6log(const char *function, int pri, char *format, ...)
{
// Pick up debug requests and decide if we are logging them
if (!debug && pri>=LOG_DEBUG)
{
return 0; // Silent...
}
// Check for extra super power debug
if ( (debug!=2) && (pri == LOG_DEBUG2) )
{
return 0; // Silent...
}
// Normalise the debug level
if( pri==LOG_DEBUG2)
{
pri=LOG_DEBUG; // Since only we understand the two levels
}
// Artificial blocking of code to improve efficiency... if the compiler plays ball. :-)
{
time_t now;
struct tm *timenow;
char timestamp[128], obuff[2048];
va_list param;
va_start(param, format);
vsnprintf(obuff, sizeof(obuff), format, param);
now = time(NULL);
timenow = localtime(&now);
(void) strftime(timestamp, sizeof(timestamp), LOGTIMEFORMAT, timenow);
switch (logging) {
case USE_FILE:
fprintf(logFileFD, "[%s] %s: %s\n", timestamp, function, obuff);
break;
case USE_SYSLOG:
syslog(pri, "%s: %s\n", function, obuff);
break;
case USE_STD:
if (pri <= LOG_ERR)
fprintf(stderr, "[%s] %s: %s\n", timestamp, function, obuff);
else
fprintf(stdout, "[%s] %s: %s\n", timestamp, function, obuff);
break;
}
va_end(param);
return 0;
}
}
/*****************************************************************************
* print_addr
* Convert ipv6 address to string representation.
*
* Inputs:
* const struct in6_addr * addr
* Binary ipv6 address
*
* Outputs:
* char * str
* String representation, *not* fully padded.
*
* Return:
* void
*
* Notes:
* Compare with print_addr16 - this version does not pad.
*/
void print_addr(struct in6_addr *addr, char *str)
{
const char *res;
res = inet_ntop(AF_INET6, (void *)addr, str, INET6_ADDRSTRLEN);
if (res == NULL)
{
flog(LOG_ERR, "print_addr: inet_ntop: %s", strerror(errno));
strcpy(str, "[invalid address]");
}
}
/*****************************************************************************
* build_addr
* Convert char represetation of ipv6 addr to binary form.
*
* Inputs:
* char * str
* String representation, fully padded.
*
* Outputs:
* const struct in6_addr * addr
* Binary ipv6 address
*
* Return:
* return code from inet_pton
* 1 => Good, else Bad
*/
int build_addr(char *str, struct in6_addr *addr)
{
int ret;
flog(LOG_DEBUG2, "called with address %s", str);
ret = inet_pton(AF_INET6, str, (void *)addr);
if (ret == 1)
flog(LOG_DEBUG2, "inet_pton OK");
else if(ret == 0)
flog(LOG_ERR, "invalid input address");
else
flog(LOG_ERR, "inet_pton: %s", strerror(errno));
return ret;
}
/*****************************************************************************
* print_addr16
* Print ipv6 address in fully expanded 64-bit char form
*
* Inputs:
* const struct in6_addr * addr
* Binary ipv6 address
*
* Outputs:
* char * str
* String representation, fully padded.
*
* Return:
* void
*/
void print_addr16(const struct in6_addr * addr, char * str)
{
sprintf(str, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
(int)addr->s6_addr[0], (int)addr->s6_addr[1],
(int)addr->s6_addr[2], (int)addr->s6_addr[3],
(int)addr->s6_addr[4], (int)addr->s6_addr[5],
(int)addr->s6_addr[6], (int)addr->s6_addr[7],
(int)addr->s6_addr[8], (int)addr->s6_addr[9],
(int)addr->s6_addr[10], (int)addr->s6_addr[11],
(int)addr->s6_addr[12], (int)addr->s6_addr[13],
(int)addr->s6_addr[14], (int)addr->s6_addr[15]);
}
/*****************************************************************************
* prefixset
* Take prefix in the form 1111:2222:3333:4444: and pad it to the
* full length
*
* Inputs:
* char * px
* String representation, fully padded.
*
* Outputs:
* As input
*
* Return:
* -1 on error, else bit length of unpadded prefix.
*
* Note:
* IMPORTANT! px *must* be big enough to hold full ipv6 string
*/
int prefixset(char px[])
{
size_t len;
int missing, c1, c2;
char suffix[INET6_ADDRSTRLEN];
// First we must ensure fully padded with leading 0s
for(c1=0; c1<INET6_ADDRSTRLEN; c1+=5 )
{
len = strlen(px);
for(c2 = 0; c2 < 4; c2++)
{
if (px[c1+c2] != ':')
continue;
else
break;
}
missing = abs(c2-4);
if ( (missing>=1) && (missing<=3) )
{
strcpy( suffix, &px[c1]); // Grab the tail
memset(&px[c1], '0', missing); // pad it with missing zeros
px[c1+missing] = '\0'; // Re-terminate
strcat(px, suffix); // Add the tail back
}
}
flog(LOG_DEBUG2, "String after padding: %s", px);
// Do we have a '::' on the end we need to trim?
len = strlen(px);
if ( (px[len-1] == ':') && (px[len-2] == ':') )
{
flog(LOG_DEBUG2, "Spotted double :: termination of prefix.");
px[len-1] = '\0';
}
len = strlen(px);
switch (len) {
case 5:
strcat(px, "0000:0000:0000:0000:0000:0000:0000");
return 16;
case 10:
strcat(px, "0000:0000:0000:0000:0000:0000");
return 32;
case 15:
strcat(px, "0000:0000:0000:0000:0000");
return 48;
case 20:
strcat(px, "0000:0000:0000:0000");
return 64;
case 25:
strcat(px, "0000:0000:0000");
return 80;
case 30:
strcat(px, "0000:0000");
return 96;
case 35:
strcat(px, "0000");
return 112;
case 39:
flog(LOG_ERR, "Full 128-bits defined as the configured prefix. Sure???");
return 128;
default:
flog(LOG_ERR, "configured prefix not correctly formatted (len = %d)", len);
return -1;
}
}
/*****************************************************************************
* stripwhitespace
* Tidy up lines of text from the config file.
*
* Inputs:
* char * str
* String to be tidied.
*
* Outputs:
* As input
*
* Return:
* void
*/
void stripwhitespace(char *str)
{
char *p1 = str;
char *p2 = str;
p1=str;
while(*p1 != 0) {
if(isspace(*p1))
++p1;
else
*p2++ = *p1++;
}
*p2=0;
}
/*****************************************************************************
* dumpHex
* Take a lump of binary data (like an ethernet frame!) and print it in hex.
* Useful for debugging!
*
* Inputs:
* unsigned char *data
* Lump of data
* unsigned int len
* Amount of data
*
* Outputs:
* Via printf to stdio
*
* Return:
* void
*/
void dumpHex(unsigned char *data, unsigned int len)
{
int ix;
printf("Dumping %d bytes of hex:\n>>>>>", len);
for(ix=0; ix < len; ix++)
printf("%02x", data[ix]);
printf("<<<<<\n");
}
/*****************************************************************************
* getLinkaddress
* Get the link-level address (i.e. MAC address) for an interface.
*
* Inputs:
* char * iface
* String containing the interface name (e.g. "eth1")
*
* Outputs:
* unsigned char * link
* String of the form "00:12:34:56:78:90"
*
* Return:
* 0 is successful,
* 1 if error.
*/
int getLinkaddress( char * iface, unsigned char * link) {
int sockfd, io;
struct ifreq ifr;
strncpy( ifr.ifr_name, iface, INTERFACE_STRLEN );
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
flog(LOG_ERR, "failed to open a test SOCK_STREAM.");
return 1;
}
io = ioctl(sockfd, SIOCGIFHWADDR, (char *)&ifr);
if(io < 0) {
flog(LOG_ERR, "octl failed to obtain link address");
close(sockfd);
return 1;
}
memcpy(link, (unsigned char *)ifr.ifr_ifru.ifru_hwaddr.sa_data, 6);
close(sockfd);
return 0;
}
//*******************************************************
// Take the supplied filename and open it for logging use.
// Upon return, logFileFD set unless we failed.
int openLog(char *logFileName)
{
if (logging == USE_FILE)
{
if ((logFileFD = fopen(logFileName, "a")) == NULL)
{
fprintf(stderr, "Can't open %s: %s\n", logFileName, strerror(errno));
return (-1);
}
// Set line-buffering so we don't need to explicitly fflush()
// when we write via npd6log
setlinebuf(logFileFD);
return 0;
}
if (logging == USE_SYSLOG)
{
openlog("npd6", (LOG_NDELAY|LOG_PID),LOG_DAEMON);
return 0;
}
// Error
return -1;
}
//*******************************************************
// Just display the version and return.
void showVersion(void)
{
printf("npd6 - version %s\n", BUILDREV);
printf("\nCopyright (C) 2011-2013 Sean Groarke\n\n");
}
void dropdead(void)
{
int loop;
/* We're dying, so tidy up*/
/* Restore interface flags and close sockets */
for (loop=0; loop<interfaceCount; loop++)
{
if_allmulti(interfaces[loop].nameStr, interfaces[loop].multiStatus);
close( interfaces[loop].pktSock );
close( interfaces[loop].icmpSock );
}
flog(LOG_ERR, "Tidied up and now exiting. Goodbye.");
exit(0);
}
/*****************************************************************************
* dumpData
* Dump internal data. Initially this will mean the set of collected
* target addresses seen (if that option is enabled)
*
* Inputs:
* tRoot is the tree of collected targets.
*
* Outputs:
* Data is dumped to the defined log.
*
* Return:
* Void
*/
void dumpAddressData(void)
{
if (!collectTargets)
{
flog(LOG_INFO, "Not dumping collected addresses - feature disabled via config.");
return;
}
flog(LOG_INFO, "====================================");
flog(LOG_INFO, "Dumping list of targets seen so far:");
flog(LOG_INFO, "------------------------------------");
twalk(tRoot, tDump);
if (tEntries == collectTargets)
{
flog(LOG_INFO, "(reached the configured limit - there were maybe more.)");
}
flog(LOG_INFO, "Total unique targets seen: %d", tEntries);
flog(LOG_INFO, "====================================");
}
/*****************************************************************************
* storeTarget
* Look in tRoot tree to see if we have it already. If we don't store
* it in the tree. If we do have it, then ignore.
*
* Inputs:
* in6_addr *Target - this is the newly seen target to check
*
* Outputs:
* tRoot has a new item added if the address was new.
*
* Return:
* Void
*/
void storeTarget(struct in6_addr *newTarget)
{
struct in6_addr *ptr;
// Take a permanent copy of the target
ptr = (struct in6_addr *)malloc(sizeof(struct in6_addr) );
if (!ptr)
{
flog(LOG_ERR, "Malloc failed. Ignoring.");
return;
}
memcpy(ptr, newTarget, sizeof(struct in6_addr) );
// We can't just tsearch() it into the tree, as there's no
// way to then know if the entry already existed or is now newly created.
// Hence we can't know to bump the count. So we tfind() first
// and only tsearch() if required. In a typical net the initial
// tfind() is going to return a result almost all the time, so the
// overhead of this double-call is actually low.
if ( tfind( (void *)ptr, &tRoot, tCompare) == NULL )
{
if (tEntries >= collectTargets)
{
flog(LOG_INFO, "Reached max threshold of recorded targets (%d). Not recording.", collectTargets);
return;
}
// New entry
flog(LOG_DEBUG2, "New entry - recording.");
if ( tsearch( (void *)ptr, &tRoot, tCompare) == NULL)
{
flog(LOG_ERR, "tsearch failed. Cannot record entry.");
free((void *)ptr);
return;
}
else
{
tEntries++;
}
}
else
{
free((void *)ptr);
flog(LOG_DEBUG2, "Entry already recorded. Ignoring.");
}
}
/*****************************************************************************
* tCompare
* This is the compare fn used by the tree handler.
*
* Inputs:
* The two generic pointers are struct in6_addr *.
*
* Outputs:
* None.
*
* Return:
* 0 if item already present, 1 if it was new.
*
* Notes:
* Need to compare two 128 bit numbers! Yucky.
* On 64-bit, we could assume long int => 64 bit, but
* given we may be on 32-bit, need to assume long int
* is max 32 bit, as per ANSI.
*
* We also make use of the underlying structure of in6_addr,
* which is int[16]
*
* Needs to be moderately efficient, since if we're recording
* a lot of addresses we call this via tfind/tsearch quite a lot,
* so we do minimal-comparison.
*/
int tCompare(const void *pa, const void *pb)
{
int paI=0, pbI=0;
int idx;
for(idx=0; idx<=15; idx++)
{
paI = ((struct in6_addr *)pa)->s6_addr[idx];
pbI = ((struct in6_addr *)pb)->s6_addr[idx];
if (paI == pbI)
continue;
if (paI < pbI)
return -1;
else
return 1;
};
// If we reach here, the items were identical
return 0;
}
/*****************************************************************************
* tDump
* This is the action used when walking tRoot from dumpData()
*
* Inputs:
* node, type of visit, depth - Check the man page for more...
*
* Outputs:
* Data dumped to log.
*
* Return:
* void
*/
void tDump(const void *nodep, const VISIT which, const int depth)
{
struct in6_addr *data;
char addressString[INET6_ADDRSTRLEN];
switch (which) {
case preorder:
case endorder:
break;
case postorder:
case leaf:
data = *(struct in6_addr **) nodep;
print_addr(data, addressString);
flog(LOG_INFO, "Address: %s", addressString);
break;
}
}
/*****************************************************************************
* storeListEntry
*
* Inputs:
* in6_addr *Target - this is the newly seen target to check
*
* Outputs:
* lRoot has a new item added if the address was new.
*
* Return:
* Void
*/
void storeListEntry(struct in6_addr *newEntry)
{
struct in6_addr *ptr;
// Take a permanenet copy of the target
ptr = (struct in6_addr *)malloc(sizeof(struct in6_addr) );
if (!ptr)
{
flog(LOG_ERR, "Malloc failed. Ignoring.");
return;
}
memcpy(ptr, newEntry, sizeof(struct in6_addr) );
if ( tfind( (void *)ptr, &lRoot, tCompare) == NULL )
{
// New entry
flog(LOG_DEBUG2, "New list entry");
if ( tsearch( (void *)ptr, &lRoot, tCompare) == NULL)
{
flog(LOG_ERR, "tsearch failed. Cannot record entry.");
free((void *)ptr);
return;
}
}
else
{
free((void *)ptr);
flog(LOG_ERR, "Dupe list entry. Ignoring.");
}
}