524 lines
19 KiB
C
524 lines
19 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"
|
|
|
|
#include "expintf.h"
|
|
|
|
/*****************************************************************************
|
|
* processNS
|
|
* Takes a received Neighbor Solicitation and handles it. Main logic is:
|
|
* - bit of extra validation.
|
|
* - determine who it's asking about.
|
|
* - see if that matches the prefix we are looking after.
|
|
* - if it does, send a Neighbor Advertisement.
|
|
*
|
|
* For more, see the inline comments - There's a lot going on here.
|
|
*
|
|
* Inputs:
|
|
* char *msg
|
|
* The received NS.
|
|
* int len
|
|
* The length of the received (candidate) NS.
|
|
* *** This has already been sanity checked back in the callers ***
|
|
*
|
|
* Outputs:
|
|
* Potentially, sends the Neighbor Advertisement.
|
|
*
|
|
* Return:
|
|
* void
|
|
*
|
|
*/
|
|
void processNS( int ifIndex,
|
|
unsigned char *msg,
|
|
unsigned int len)
|
|
{
|
|
// String representations of the various addresses
|
|
char targetaddr_str[INET6_ADDRSTRLEN];
|
|
char prefixaddr_str[INET6_ADDRSTRLEN];
|
|
char srcaddr_str[INET6_ADDRSTRLEN];
|
|
char dstaddr_str[INET6_ADDRSTRLEN];
|
|
|
|
// Offsets into the received packet
|
|
struct ip6_hdr *ip6h =
|
|
(struct ip6_hdr *)(msg + ETH_HLEN);
|
|
struct icmp6_hdr *icmph =
|
|
(struct icmp6_hdr *)(msg + ETH_HLEN + sizeof( struct ip6_hdr));
|
|
struct nd_neighbor_solicit *ns =
|
|
(struct nd_neighbor_solicit *)(msg + ETH_HLEN + sizeof( struct ip6_hdr));
|
|
|
|
// For the interfaceIdx
|
|
struct in6_addr prefixaddr = interfaces[ifIndex].prefix;
|
|
int prefixaddrlen = interfaces[ifIndex].prefixLen;
|
|
unsigned char *linkAddr = interfaces[ifIndex].linkAddr;
|
|
int interfaceIdx = interfaces[ifIndex].index;
|
|
|
|
// Extracted from the received packet
|
|
struct in6_addr *srcaddr;
|
|
struct in6_addr *dstaddr;
|
|
struct in6_addr *targetaddr;
|
|
unsigned int multicastNS;
|
|
|
|
// For outgoing NA
|
|
struct in6_addr srcLinkAddr = IN6ADDR_ANY_INIT;
|
|
struct in6_pktinfo *pkt_info;
|
|
struct sockaddr_in6 sockaddr;
|
|
unsigned char nabuff[MAX_PKT_BUFF];
|
|
struct nd_neighbor_advert *nad;
|
|
size_t iovlen=0;
|
|
struct iovec iov;
|
|
struct cmsghdr *cmsg;
|
|
char __attribute__((aligned(8))) chdr[CMSG_SPACE(sizeof(struct in6_pktinfo))];
|
|
struct msghdr mhdr;
|
|
ssize_t err;
|
|
struct nd_opt_hdr *opthdr;
|
|
void *optdata;
|
|
|
|
|
|
// Validate ICMP packet type, to ensure filter was correct
|
|
// In theory not required, as the filter CAN'T be wrong...!
|
|
if ( icmph->icmp6_type == ND_NEIGHBOR_SOLICIT )
|
|
{
|
|
flog(LOG_DEBUG2, "Confirmed packet as icmp6 Neighbor Solicitation.");
|
|
srcaddr = &ip6h->ip6_src;
|
|
dstaddr = &ip6h->ip6_dst;
|
|
if (debug)
|
|
{
|
|
print_addr(srcaddr, srcaddr_str);
|
|
print_addr(dstaddr, dstaddr_str);
|
|
flog( LOG_DEBUG, "src addr = %s", srcaddr_str);
|
|
flog( LOG_DEBUG, "dst addr = %s", dstaddr_str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flog(LOG_ERR, "Received impossible packet... filter failed. Oooops.");
|
|
return;
|
|
}
|
|
|
|
// Bug 27 - Handle DAD NS as per RFC4862, 5.4.3
|
|
if ( IN6_IS_ADDR_UNSPECIFIED(srcaddr) )
|
|
{
|
|
flog(LOG_DEBUG, "Unspecified src addr - DAD activity. Ignoring NS.");
|
|
return;
|
|
}
|
|
|
|
// Based upon the dstaddr, record if this was a unicast or multicast NS.
|
|
// If unicast, we'll use that later when we decide whether to add the
|
|
// target link-layer option to any outgoing NA.
|
|
if ( IN6_IS_ADDR_MULTICAST(dstaddr) )
|
|
{
|
|
// This was a multicast NS
|
|
flog(LOG_DEBUG2, "Multicast NS");
|
|
multicastNS = 1;
|
|
}else
|
|
{
|
|
// This was a unicast NS
|
|
flog(LOG_DEBUG2, "Unicast NS");
|
|
multicastNS=0;
|
|
}
|
|
|
|
// Within the NS, who are they looking for?
|
|
targetaddr = (struct in6_addr *)&(ns->nd_ns_target);
|
|
if (debug || listLog)
|
|
{
|
|
print_addr16(targetaddr, targetaddr_str);
|
|
print_addr16(&prefixaddr, prefixaddr_str);
|
|
flog(LOG_DEBUG, "NS target addr: %s", targetaddr_str);
|
|
flog(LOG_DEBUG, "Local prefix: %s", prefixaddr_str);
|
|
}
|
|
|
|
// If tgt-addr == dst-addr then ignore this, as the automatic mechanisms
|
|
// will reply themselves - we don't need to.
|
|
if ( nsIgnoreLocal && IN6_ARE_ADDR_EQUAL(targetaddr, dstaddr) )
|
|
{
|
|
flog(LOG_DEBUG, "tgt==dst - Ignore.");
|
|
return;
|
|
}
|
|
|
|
// Check for black or white listing compliance
|
|
switch (listType) {
|
|
case NOLIST:
|
|
flog(LOG_DEBUG2, "Neither white nor black listing in operation.");
|
|
break;
|
|
|
|
case BLACKLIST:
|
|
// See if the address matches an expression
|
|
if((compareExpression(targetaddr) == 1))
|
|
{
|
|
flog(LISTLOGGING, "NS for blacklisted EXPR address: %s", targetaddr_str);
|
|
return; // Abandon
|
|
}
|
|
// If active and tgt is in the list, bail.
|
|
if ( tfind( (void *)targetaddr, &lRoot, tCompare) )
|
|
{
|
|
flog(LISTLOGGING, "NS for blacklisted specific addr: %s", targetaddr_str);
|
|
return; //Abandon
|
|
}
|
|
break;
|
|
|
|
case WHITELIST:
|
|
// See if the address matches an expression
|
|
if((compareExpression(targetaddr) == 1))
|
|
{
|
|
flog(LISTLOGGING, "NS for whitelisted EXPR: %s", targetaddr_str);
|
|
break; // Don't check further - we got a hit.
|
|
}
|
|
|
|
// If active and tgt is NOT in the list (and didn't match an expr above), bail.
|
|
if ( tfind( (void *)targetaddr, &lRoot, tCompare) )
|
|
{
|
|
flog(LISTLOGGING, "NS for specific addr whitelisted: %s", targetaddr_str);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// We have whitelisting in operation but failed to match either type.
|
|
// Log it if required.
|
|
flog(LOG_DEBUG, "No whitelist match for: %s", targetaddr_str);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Does it match our configured prefix that we're interested in?
|
|
if (! addr6match( targetaddr, &prefixaddr, prefixaddrlen) )
|
|
{
|
|
flog(LOG_DEBUG, "Target/:prefix - Ignore NS.");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
flog(LOG_DEBUG, "Target:prefix - Build NA response.");
|
|
|
|
// If configured, log target to list
|
|
if (collectTargets)
|
|
{
|
|
flog(LOG_DEBUG, "Store target to list.");
|
|
storeTarget( targetaddr );
|
|
}
|
|
|
|
// Start building up the header for the packet
|
|
memset(( void *)&sockaddr, 0, sizeof(struct sockaddr_in6));
|
|
sockaddr.sin6_family = AF_INET6;
|
|
sockaddr.sin6_port = htons(IPPROTO_ICMPV6);
|
|
|
|
// Set the destination of the NA
|
|
memcpy(&sockaddr.sin6_addr, srcaddr, sizeof(struct in6_addr));
|
|
|
|
// Set up the NA itself
|
|
memset( nabuff, 0, sizeof(nabuff) );
|
|
nad = (struct nd_neighbor_advert *)nabuff;
|
|
nad->nd_na_type = ND_NEIGHBOR_ADVERT;
|
|
nad->nd_na_code = 0;
|
|
nad->nd_na_cksum = 0;
|
|
if (naRouter)
|
|
{
|
|
nad->nd_na_flags_reserved |= ND_NA_FLAG_SOLICITED | ND_NA_FLAG_ROUTER;
|
|
}
|
|
else
|
|
{
|
|
nad->nd_na_flags_reserved |= ND_NA_FLAG_SOLICITED;
|
|
}
|
|
|
|
memcpy(&(nad->nd_na_target), targetaddr, sizeof(struct in6_addr) );
|
|
|
|
if (multicastNS || naLinkOptFlag)
|
|
{
|
|
// If the NS that came in was to a multicast address
|
|
// or if we have forced the option for all packets anyway
|
|
// then add a target link-layer option to the outgoing NA.
|
|
// Per rfc, we must add dest link-addr option for NSs that came
|
|
// to the multicast group addr.
|
|
opthdr = (struct nd_opt_hdr *)&nabuff[sizeof(struct nd_neighbor_advert)] ;
|
|
opthdr->nd_opt_type = ND_OPT_TARGET_LINKADDR;
|
|
opthdr->nd_opt_len = 1; // Units of 8-octets
|
|
optdata = (unsigned char *) (opthdr + 1);
|
|
memcpy( optdata, linkAddr, 6);
|
|
|
|
// Build the io vector
|
|
iovlen = sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + ETH_ALEN;
|
|
iov.iov_len = iovlen;
|
|
iov.iov_base = (caddr_t) nabuff;
|
|
} else
|
|
{
|
|
// The NS was unicast AND the config option was unset.
|
|
// Build the io vector
|
|
iovlen = sizeof(struct nd_neighbor_advert);
|
|
iov.iov_len = iovlen;
|
|
iov.iov_base = (caddr_t) nabuff;
|
|
}
|
|
|
|
// Build the cmsg
|
|
memset(chdr, 0, sizeof(chdr) );
|
|
cmsg = (struct cmsghdr *) chdr;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo) );
|
|
cmsg->cmsg_level = IPPROTO_IPV6;
|
|
cmsg->cmsg_type = IPV6_PKTINFO;
|
|
|
|
pkt_info = (struct in6_pktinfo *)CMSG_DATA(cmsg);
|
|
|
|
// Set src (sending) addr and outgoing i/f for the datagram
|
|
memcpy(&pkt_info->ipi6_addr, &srcLinkAddr, sizeof(struct in6_addr) );
|
|
pkt_info->ipi6_ifindex = interfaceIdx;
|
|
|
|
// Build the mhdr
|
|
memset(&mhdr, 0, sizeof(mhdr) );
|
|
mhdr.msg_name = (caddr_t)&sockaddr;
|
|
mhdr.msg_namelen = sizeof(sockaddr);
|
|
mhdr.msg_iov = &iov;
|
|
mhdr.msg_iovlen = 1;
|
|
mhdr.msg_control = (void *) cmsg;
|
|
mhdr.msg_controllen = sizeof(chdr);
|
|
|
|
flog(LOG_DEBUG2, "Outbound message built");
|
|
|
|
err = sendmsg( interfaces[ifIndex].icmpSock, &mhdr, 0);
|
|
if (err < 0)
|
|
flog(LOG_ERR, "sendmsg returned with error %d = %s", errno, strerror(errno));
|
|
else
|
|
flog(LOG_DEBUG2, "sendmsg completed OK");
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* processICMP
|
|
* Takes a received ICMP message and handles it. At first, we don't
|
|
* do too much. Based upon NFR 60 we are going to look out for RAs,
|
|
* extract useful data and log it.
|
|
*
|
|
* Later on we may go further with that information...
|
|
*
|
|
* Inputs:
|
|
* char *msg
|
|
* The received ICMP6.
|
|
* int len
|
|
* The length of the received data
|
|
* *** This has already been sanity checked back in the callers ***
|
|
*
|
|
* Outputs:
|
|
* As per above, likely just a log/debug for now.
|
|
*
|
|
* Return:
|
|
* void
|
|
*
|
|
*/
|
|
void processICMP( int ifIndex,
|
|
unsigned char *msg,
|
|
unsigned int len,
|
|
struct in6_addr *addr6)
|
|
{
|
|
// Offsets into the received packet
|
|
struct icmp6_hdr *icmph =
|
|
(struct icmp6_hdr *)(msg);
|
|
struct nd_router_advert *ra =
|
|
(struct nd_router_advert *)(msg + sizeof(struct icmp6_hdr));
|
|
|
|
uint32_t reachableT = ra->nd_ra_reachable;
|
|
uint32_t retransmitT = ra->nd_ra_retransmit;
|
|
int curHopLimit = ra->nd_ra_curhoplimit;
|
|
int rtrLifetime = ra->nd_ra_router_lifetime;
|
|
|
|
int counter = 0;
|
|
char addr6_str[INET6_ADDRSTRLEN];
|
|
|
|
// May not exist - will check
|
|
struct nd_opt_hdr *optHdr =
|
|
(struct nd_opt_hdr *)(msg + sizeof(struct nd_router_advert));
|
|
|
|
flog(LOG_DEBUG, "Check for RA in received ICMP6.");
|
|
|
|
if ( icmph->icmp6_type == ND_ROUTER_ADVERT )
|
|
{
|
|
print_addr(addr6, addr6_str);
|
|
flog(LOG_INFO, "RA received from address: %s", addr6_str);
|
|
flog(LOG_DEBUG, "Reachable timer = %ld, retransmit timer = %ld", ntohl(reachableT), ntohl(retransmitT));
|
|
flog(LOG_DEBUG, "Cur Hop Limit = %d, Router Lifetime = %d", curHopLimit, rtrLifetime);
|
|
|
|
counter = sizeof(struct nd_router_advert);
|
|
while (counter < len)
|
|
{
|
|
uint8_t optionLen; /* n.b. Octets */
|
|
uint8_t prefixLen;
|
|
uint32_t prefixValidTime, prefixPreferredTime;
|
|
struct in6_addr prefixPrefix;
|
|
char prefixPrefix_str[INET6_ADDRSTRLEN];
|
|
struct nd_opt_prefix_info *prefixInfo;
|
|
unsigned char *linkAddr;
|
|
int watchDog=0;
|
|
|
|
// So offset optHdr is now valid and points to 1 or more options
|
|
switch(optHdr->nd_opt_type) {
|
|
case ND_OPT_SOURCE_LINKADDR:
|
|
flog(LOG_DEBUG, "RA-opt received: Source Link Address");
|
|
linkAddr = ((unsigned char *)(optHdr) + 2);
|
|
flog(LOG_DEBUG, "Link address: %02x:%02x:%02x:%02x:%02x:%02x",
|
|
linkAddr[0], linkAddr[1], linkAddr[2], linkAddr[3], linkAddr[4], linkAddr[5]);
|
|
break;
|
|
|
|
case ND_OPT_TARGET_LINKADDR:
|
|
flog(LOG_DEBUG, "RA-opt received: Target Link Address");
|
|
linkAddr = ((unsigned char *)(optHdr) + 2);
|
|
flog(LOG_DEBUG, "Link address: %02x:%02x:%02x:%02x:%02x:%02x",
|
|
linkAddr[0], linkAddr[1], linkAddr[2], linkAddr[3], linkAddr[4], linkAddr[5]);
|
|
break;
|
|
|
|
case ND_OPT_PREFIX_INFORMATION:
|
|
flog(LOG_INFO, "RA-opt received: Prefix Info");
|
|
prefixInfo = (struct nd_opt_prefix_info *)optHdr;
|
|
prefixLen = prefixInfo->nd_opt_pi_prefix_len;
|
|
prefixValidTime = prefixInfo->nd_opt_pi_valid_time;
|
|
prefixPreferredTime = prefixInfo->nd_opt_pi_preferred_time;
|
|
prefixPrefix = prefixInfo->nd_opt_pi_prefix;
|
|
|
|
print_addr(&prefixPrefix, prefixPrefix_str);
|
|
flog(LOG_INFO, "Received prefix is: %s", prefixPrefix_str);
|
|
flog(LOG_INFO, "Prefix length: %d", prefixLen);
|
|
flog(LOG_INFO, "Valid time: %ld", ntohl(prefixValidTime));
|
|
flog(LOG_INFO, "Preferred time: %ld", ntohl(prefixPreferredTime));
|
|
break;
|
|
|
|
case ND_OPT_REDIRECTED_HEADER:
|
|
flog(LOG_DEBUG, "RA-opt received: Redirected Header");
|
|
break;
|
|
|
|
case ND_OPT_MTU:
|
|
flog(LOG_DEBUG, "RA-opt received: MTU");
|
|
break;
|
|
|
|
case ND_OPT_RTR_ADV_INTERVAL:
|
|
flog(LOG_DEBUG, "RA-opt received: RA Interval");
|
|
break;
|
|
|
|
case ND_OPT_HOME_AGENT_INFO:
|
|
flog(LOG_DEBUG, "RA-opt received: Home Agent Info");
|
|
break;
|
|
|
|
default:
|
|
// *** Important default ***
|
|
// Got an option that we cannot recognise - log and skip it
|
|
flog(LOG_ERR, "Had option type = %d - do not recognise.", optHdr->nd_opt_type);
|
|
}
|
|
|
|
// Sanity check to catch runaway situation with corrupt packet (malicious or otherwise!)
|
|
watchDog++;
|
|
if(watchDog > 20) // 20 seems enough to say STOP!
|
|
{
|
|
flog(LOG_ERR, "Tripped watchdog in ICMP option decoding... Something very odd...");
|
|
return;
|
|
}
|
|
|
|
// Increment to (possible) next opt
|
|
optionLen = (optHdr->nd_opt_len) * 8;
|
|
counter += optionLen;
|
|
// 32/64-bit agnostic
|
|
optHdr = (struct nd_opt_hdr *) ((char *)optHdr + optionLen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flog(LOG_ERR, "Received ICMP6 - did not recognise it.");
|
|
flog(LOG_ERR, "Type was %d", icmph->icmp6_type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
* addr6match
|
|
* Compare two binary ipv6 addresses and see if they match
|
|
* in the first N bits.
|
|
*
|
|
* Inputs:
|
|
* a1 & a2 are the addresses to be compared, in form in6_addr.
|
|
* bits is the number of bits to compare, starting from the left.
|
|
*
|
|
* Outputs:
|
|
* void
|
|
*
|
|
* Return:
|
|
* 1 if we match, else 0.
|
|
*
|
|
*/
|
|
int addr6match( struct in6_addr *a1, struct in6_addr *a2, int bits)
|
|
{
|
|
int idx, bdx;
|
|
unsigned int mask;
|
|
|
|
flog(LOG_DEBUG2, "Called to match up to %d bits.", bits);
|
|
|
|
if (bits > 128)
|
|
{
|
|
flog(LOG_ERR, "Bits > 128 (%d) does not make sense.", bits);
|
|
return 0;
|
|
}
|
|
|
|
// The approach here is to gallop along the address comparing full octets for as far as possible.
|
|
// Then when/if we reach a non-octet aligned point, we deal with that.
|
|
// Since vast majority of folks will have octet aligned prefixes, this is highly efficient
|
|
for (bdx=bits, idx=0; bdx>0; bdx-=8, idx++)
|
|
{
|
|
if (bdx >= 8)
|
|
{
|
|
// We can compare a full 8-bit comparison - no masking yet
|
|
if ( a1->s6_addr[idx] != a2->s6_addr[idx] )
|
|
{
|
|
//Failed to match
|
|
flog(LOG_DEBUG2, "Failed in 8-bit match test.idx = %d, bdx = %d", idx, bdx);
|
|
flog(LOG_DEBUG2, "a1 value: %2x a2 value: %2x", a1->s6_addr[idx], a2->s6_addr[idx]);
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We are in the end-zone - masking required
|
|
switch (bdx) {
|
|
case 7: mask=0xfe; break;
|
|
case 6: mask=0xfc; break;
|
|
case 5: mask=0xf8; break;
|
|
case 4: mask=0xf0; break;
|
|
case 3: mask=0xe0; break;
|
|
case 2: mask=0xc0; break;
|
|
case 1: mask=0x80; break;
|
|
}
|
|
if ( ((a1->s6_addr[idx])&mask) != a2->s6_addr[idx] )
|
|
{
|
|
//Failed to match
|
|
flog(LOG_DEBUG2, "Failed in mask match test.idx = %d, bdx = %d, mask = %04x", idx, bdx, mask);
|
|
flog(LOG_DEBUG2, "a1 value: %04x a2 value: %04x", a1->s6_addr[idx], a2->s6_addr[idx]);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
flog(LOG_DEBUG2, "Target and prefix matched up to bit position %d", bits);
|
|
|
|
return 1;
|
|
}
|
|
|