#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include "rtadvd.h"
#include "rrenum.h"
#include "if.h"
#define RR_ISSET_SEGNUM(segnum_bits, segnum) \
((((segnum_bits)[(segnum) >> 5]) & (1 << ((segnum) & 31))) != 0)
#define RR_SET_SEGNUM(segnum_bits, segnum) \
(((segnum_bits)[(segnum) >> 5]) |= (1 << ((segnum) & 31)))
struct rr_operation {
u_long rro_seqnum;
u_long rro_segnum_bits[8];
};
static struct rr_operation rro;
static int rr_rcvifindex;
static int rrcmd2pco[RPM_PCO_MAX] = {
0,
SIOCAIFPREFIX_IN6,
SIOCCIFPREFIX_IN6,
SIOCSGIFPREFIX_IN6
};
static int s = -1;
static int
rr_pco_check(int len, struct rr_pco_match *rpm)
{
struct rr_pco_use *rpu, *rpulim;
int checklen;
if ((rpm->rpm_len - 3) < 0 ||
(rpm->rpm_len - 3) & 0x3) {
syslog(LOG_WARNING, "<%s> rpm_len %d is not 4N * 3",
__func__, rpm->rpm_len);
return 1;
}
switch (rpm->rpm_code) {
case RPM_PCO_ADD:
case RPM_PCO_CHANGE:
case RPM_PCO_SETGLOBAL:
break;
default:
syslog(LOG_WARNING, "<%s> unknown rpm_code %d", __func__,
rpm->rpm_code);
return 1;
}
if (rpm->rpm_matchlen > 128) {
syslog(LOG_WARNING, "<%s> rpm_matchlen %d is over 128",
__func__, rpm->rpm_matchlen);
return 1;
}
for (rpu = (struct rr_pco_use *)(rpm + 1),
rpulim = (struct rr_pco_use *)((char *)rpm + len);
rpu < rpulim;
rpu += 1) {
checklen = rpu->rpu_uselen;
checklen += rpu->rpu_keeplen;
if (checklen > 128) {
syslog(LOG_WARNING, "<%s> sum of rpu_uselen %d and"
" rpu_keeplen %d is %d(over 128)",
__func__, rpu->rpu_uselen,
rpu->rpu_keeplen,
rpu->rpu_uselen + rpu->rpu_keeplen);
return 1;
}
}
return 0;
}
static void
do_use_prefix(int len, struct rr_pco_match *rpm,
struct in6_rrenumreq *irr, int ifindex)
{
struct rr_pco_use *rpu, *rpulim;
struct rainfo *rai;
struct prefix *pp;
rpu = (struct rr_pco_use *)(rpm + 1);
rpulim = (struct rr_pco_use *)((char *)rpm + len);
if (rpu == rpulim) {
if (rpm->rpm_code == RPM_PCO_ADD)
return;
irr->irr_u_uselen = 0;
irr->irr_u_keeplen = 0;
irr->irr_raf_mask_onlink = 0;
irr->irr_raf_mask_auto = 0;
irr->irr_vltime = 0;
irr->irr_pltime = 0;
memset(&irr->irr_flags, 0, sizeof(irr->irr_flags));
irr->irr_useprefix.sin6_len = 0;
irr->irr_useprefix.sin6_family = 0;
irr->irr_useprefix.sin6_addr = in6addr_any;
if (ioctl(s, rrcmd2pco[rpm->rpm_code], (caddr_t)irr) < 0 &&
errno != EADDRNOTAVAIL)
syslog(LOG_ERR, "<%s> ioctl: %s", __func__,
strerror(errno));
return;
}
for (rpu = (struct rr_pco_use *)(rpm + 1),
rpulim = (struct rr_pco_use *)((char *)rpm + len);
rpu < rpulim;
rpu += 1) {
irr->irr_u_uselen = rpu->rpu_uselen;
irr->irr_u_keeplen = rpu->rpu_keeplen;
irr->irr_raf_mask_onlink =
!!(rpu->rpu_ramask & ICMP6_RR_PCOUSE_RAFLAGS_ONLINK);
irr->irr_raf_mask_auto =
!!(rpu->rpu_ramask & ICMP6_RR_PCOUSE_RAFLAGS_AUTO);
irr->irr_vltime = ntohl(rpu->rpu_vltime);
irr->irr_pltime = ntohl(rpu->rpu_pltime);
irr->irr_raf_onlink =
(rpu->rpu_raflags & ICMP6_RR_PCOUSE_RAFLAGS_ONLINK) == 0 ? 0 : 1;
irr->irr_raf_auto =
(rpu->rpu_raflags & ICMP6_RR_PCOUSE_RAFLAGS_AUTO) == 0 ? 0 : 1;
irr->irr_rrf_decrvalid =
(rpu->rpu_flags & ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME) == 0 ? 0 : 1;
irr->irr_rrf_decrprefd =
(rpu->rpu_flags & ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME) == 0 ? 0 : 1;
irr->irr_useprefix.sin6_len = sizeof(irr->irr_useprefix);
irr->irr_useprefix.sin6_family = AF_INET6;
irr->irr_useprefix.sin6_addr = rpu->rpu_prefix;
if (ioctl(s, rrcmd2pco[rpm->rpm_code], (caddr_t)irr) < 0 &&
errno != EADDRNOTAVAIL)
syslog(LOG_ERR, "<%s> ioctl: %s", __func__,
strerror(errno));
if (rpm->rpm_code == RPM_PCO_CHANGE &&
IN6_ARE_ADDR_EQUAL(&rpm->rpm_prefix, &rpu->rpu_prefix) &&
rpm->rpm_matchlen == rpu->rpu_uselen &&
rpu->rpu_uselen == rpu->rpu_keeplen) {
if ((rai = if_indextorainfo(ifindex)) == NULL)
continue;
for (pp = rai->prefix.next; pp != &rai->prefix;
pp = pp->next) {
struct timeval now;
if (prefix_match(&pp->prefix, pp->prefixlen,
&rpm->rpm_prefix,
rpm->rpm_matchlen)) {
pp->validlifetime = ntohl(rpu->rpu_vltime);
pp->preflifetime = ntohl(rpu->rpu_pltime);
if (irr->irr_rrf_decrvalid) {
gettimeofday(&now, 0);
pp->vltimeexpire =
now.tv_sec + pp->validlifetime;
} else
pp->vltimeexpire = 0;
if (irr->irr_rrf_decrprefd) {
gettimeofday(&now, 0);
pp->pltimeexpire =
now.tv_sec + pp->preflifetime;
} else
pp->pltimeexpire = 0;
}
}
}
}
}
static int
do_pco(struct icmp6_router_renum *rr, int len, struct rr_pco_match *rpm)
{
int ifindex = 0;
struct in6_rrenumreq irr;
if ((rr_pco_check(len, rpm) != 0))
return 1;
if (s == -1 && (s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
syslog(LOG_ERR, "<%s> socket: %s", __func__,
strerror(errno));
exit(1);
}
memset(&irr, 0, sizeof(irr));
irr.irr_origin = PR_ORIG_RR;
irr.irr_m_len = rpm->rpm_matchlen;
irr.irr_m_minlen = rpm->rpm_minlen;
irr.irr_m_maxlen = rpm->rpm_maxlen;
irr.irr_matchprefix.sin6_len = sizeof(irr.irr_matchprefix);
irr.irr_matchprefix.sin6_family = AF_INET6;
irr.irr_matchprefix.sin6_addr = rpm->rpm_prefix;
while (if_indextoname(++ifindex, irr.irr_name)) {
if ((rr->rr_flags & ICMP6_RR_FLAGS_FORCEAPPLY) == 0 &&
(iflist[ifindex]->ifm_flags & IFF_UP) == 0)
continue;
do_use_prefix(len, rpm, &irr, ifindex);
}
if (errno == ENXIO)
return 0;
else if (errno) {
syslog(LOG_ERR, "<%s> if_indextoname: %s", __func__,
strerror(errno));
return 1;
}
return 0;
}
static int
do_rr(int len, struct icmp6_router_renum *rr)
{
struct rr_pco_match *rpm;
char *cp, *lim;
lim = (char *)rr + len;
cp = (char *)(rr + 1);
len -= sizeof(struct icmp6_router_renum);
init_iflist();
while (cp < lim) {
int rpmlen;
rpm = (struct rr_pco_match *)cp;
if (len < sizeof(struct rr_pco_match)) {
tooshort:
syslog(LOG_ERR, "<%s> pkt too short. left len = %d. "
"gabage at end of pkt?", __func__, len);
return 1;
}
rpmlen = rpm->rpm_len << 3;
if (len < rpmlen)
goto tooshort;
if (do_pco(rr, rpmlen, rpm)) {
syslog(LOG_WARNING, "<%s> invalid PCO", __func__);
goto next;
}
next:
cp += rpmlen;
len -= rpmlen;
}
return 0;
}
static int
rr_command_check(int len, struct icmp6_router_renum *rr, struct in6_addr *from,
struct in6_addr *dst)
{
char ntopbuf[INET6_ADDRSTRLEN];
if (len < (sizeof(struct icmp6_router_renum) +
sizeof(struct rr_pco_match))) {
syslog(LOG_ERR, "<%s> rr_command len %d is too short",
__func__, len);
return 1;
}
if (IN6_IS_ADDR_MULTICAST(dst) && !IN6_IS_ADDR_MC_LINKLOCAL(dst) &&
!IN6_IS_ADDR_MC_SITELOCAL(dst)) {
syslog(LOG_ERR, "<%s> dst mcast addr %s is illegal",
__func__,
inet_ntop(AF_INET6, dst, ntopbuf, INET6_ADDRSTRLEN));
return 1;
}
if (rro.rro_seqnum > rr->rr_seqnum) {
syslog(LOG_WARNING,
"<%s> rcvd old seqnum %d from %s",
__func__, (u_int32_t)ntohl(rr->rr_seqnum),
inet_ntop(AF_INET6, from, ntopbuf, INET6_ADDRSTRLEN));
return 1;
}
if (rro.rro_seqnum == rr->rr_seqnum &&
(rr->rr_flags & ICMP6_RR_FLAGS_TEST) == 0 &&
RR_ISSET_SEGNUM(rro.rro_segnum_bits, rr->rr_segnum)) {
if ((rr->rr_flags & ICMP6_RR_FLAGS_REQRESULT) != 0)
syslog(LOG_WARNING,
"<%s> rcvd duped segnum %d from %s",
__func__, rr->rr_segnum,
inet_ntop(AF_INET6, from, ntopbuf,
INET6_ADDRSTRLEN));
return 0;
}
if (rro.rro_seqnum != rr->rr_seqnum) {
memset(rro.rro_segnum_bits, 0,
sizeof(rro.rro_segnum_bits));
}
rro.rro_seqnum = rr->rr_seqnum;
return 0;
}
static void
rr_command_input(int len, struct icmp6_router_renum *rr,
struct in6_addr *from, struct in6_addr *dst)
{
if (rr_command_check(len, rr, from, dst))
goto failed;
if ((rr->rr_flags & (ICMP6_RR_FLAGS_TEST|ICMP6_RR_FLAGS_REQRESULT)) ==
ICMP6_RR_FLAGS_TEST)
return;
if (do_rr(len, rr)) {
goto failed;
}
RR_SET_SEGNUM(rro.rro_segnum_bits, rr->rr_segnum);
return;
failed:
syslog(LOG_ERR, "<%s> received RR was invalid", __func__);
return;
}
void
rr_input(int len, struct icmp6_router_renum *rr, struct in6_pktinfo *pi,
struct sockaddr_in6 *from, struct in6_addr *dst)
{
char ntopbuf[2][INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
syslog(LOG_DEBUG,
"<%s> RR received from %s to %s on %s",
__func__,
inet_ntop(AF_INET6, &from->sin6_addr,
ntopbuf[0], INET6_ADDRSTRLEN),
inet_ntop(AF_INET6, &dst, ntopbuf[1], INET6_ADDRSTRLEN),
if_indextoname(pi->ipi6_ifindex, ifnamebuf));
if (len < sizeof(struct icmp6_router_renum)) {
syslog(LOG_NOTICE,
"<%s>: RR short message (size %d) from %s to %s on %s",
__func__, len,
inet_ntop(AF_INET6, &from->sin6_addr,
ntopbuf[0], INET6_ADDRSTRLEN),
inet_ntop(AF_INET6, &dst, ntopbuf[1], INET6_ADDRSTRLEN),
if_indextoname(pi->ipi6_ifindex, ifnamebuf));
return;
}
if (IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) &&
!IN6_ARE_ADDR_EQUAL(&in6a_site_allrouters, &pi->ipi6_addr)) {
syslog(LOG_NOTICE,
"<%s>: RR message with invalid destination (%s) "
"from %s on %s",
__func__,
inet_ntop(AF_INET6, &dst, ntopbuf[0], INET6_ADDRSTRLEN),
inet_ntop(AF_INET6, &from->sin6_addr,
ntopbuf[1], INET6_ADDRSTRLEN),
if_indextoname(pi->ipi6_ifindex, ifnamebuf));
return;
}
rr_rcvifindex = pi->ipi6_ifindex;
switch (rr->rr_code) {
case ICMP6_ROUTER_RENUMBERING_COMMAND:
rr_command_input(len, rr, &from->sin6_addr, dst);
break;
case ICMP6_ROUTER_RENUMBERING_RESULT:
break;
case ICMP6_ROUTER_RENUMBERING_SEQNUM_RESET:
break;
default:
syslog(LOG_ERR, "<%s> received unknown code %d",
__func__, rr->rr_code);
break;
}
return;
}