#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include "igmp.h"
#include "dvmrpd.h"
#include "dvmrp.h"
#include "dvmrpe.h"
#include "log.h"
extern struct dvmrpd_conf *deconf;
void rr_list_remove(struct route_report *);
int
send_report(struct iface *iface, struct in_addr addr, void *data, int len)
{
struct sockaddr_in dst;
struct ibuf *buf;
int ret = 0;
log_debug("send_report: interface %s addr %s",
iface->name, inet_ntoa(addr));
if (iface->passive)
return (0);
if ((buf = ibuf_open(iface->mtu - sizeof(struct ip))) == NULL)
fatal("send_report");
if (gen_dvmrp_hdr(buf, iface, DVMRP_CODE_REPORT))
goto fail;
ibuf_add(buf, data, len);
dst.sin_family = AF_INET;
dst.sin_len = sizeof(struct sockaddr_in);
dst.sin_addr.s_addr = addr.s_addr;
ret = send_packet(iface, buf, &dst);
ibuf_free(buf);
return (ret);
fail:
log_warn("send_report");
ibuf_free(buf);
return (-1);
}
void
recv_report(struct nbr *nbr, char *buf, u_int16_t len)
{
struct route_report rr;
u_int32_t netid, netmask;
u_int8_t metric, netid_len, prefixlen;
log_debug("recv_report: neighbor ID %s", inet_ntoa(nbr->id));
if ((nbr->state != NBR_STA_2_WAY) && (!nbr->compat)) {
log_warnx("recv_report: neighbor %s not in state %s",
inet_ntoa(nbr->id), "2-WAY");
return;
}
do {
memcpy(&netmask, buf, sizeof(netmask));
netmask = ntohl(netmask) >> 8;
netmask = htonl(netmask);
netmask |= htonl(0xff000000);
buf += 3;
len -= 3;
prefixlen = mask2prefixlen(netmask);
netid_len = PREFIX_SIZE(prefixlen);
do {
memcpy(&netid, buf, sizeof(netid));
netid &= netmask;
buf += netid_len;
len -= netid_len;
memcpy(&metric, buf, sizeof(metric));
buf += sizeof(metric);
len -= sizeof(metric);
rr.net.s_addr = netid;
rr.mask.s_addr = netmask;
rr.nexthop = nbr->id;
rr.metric = (metric & METRIC_MASK);
rr.ifindex = nbr->iface->ifindex;
dvmrpe_imsg_compose_rde(IMSG_ROUTE_REPORT, nbr->peerid,
0, &rr, sizeof(rr));
} while (!(metric & LAST_MASK) && (len > 0));
} while (len > 0);
return;
}
void
report_timer(int fd, short event, void *arg)
{
struct timeval tv;
dvmrpe_imsg_compose_rde(IMSG_FULL_ROUTE_REPORT, 0, 0, NULL, 0);
timerclear(&tv);
tv.tv_sec = ROUTE_REPORT_INTERVAL;
evtimer_add(&deconf->report_timer, &tv);
}
int
start_report_timer(void)
{
struct timeval tv;
timerclear(&tv);
tv.tv_sec = MIN_FLASH_UPDATE_INTERVAL;
return (evtimer_add(&deconf->report_timer, &tv));
}
int
stop_report_timer(void)
{
return (evtimer_del(&deconf->report_timer));
}
void
rr_list_add(struct rr_head *rr_list, struct route_report *rr)
{
struct rr_entry *le;
if (rr == NULL)
fatalx("rr_list_add: no route report");
if ((le = calloc(1, sizeof(*le))) == NULL)
fatal("rr_list_add");
TAILQ_INSERT_TAIL(rr_list, le, entry);
le->re = rr;
rr->refcount++;
}
void
rr_list_remove(struct route_report *rr)
{
if (--rr->refcount == 0)
free(rr);
}
void
rr_list_clr(struct rr_head *rr_list)
{
struct rr_entry *le;
while ((le = TAILQ_FIRST(rr_list)) != NULL) {
TAILQ_REMOVE(rr_list, le, entry);
rr_list_remove(le->re);
free(le);
}
}
void
rr_list_send(struct rr_head *rr_list, struct iface *xiface, struct nbr *nbr)
{
struct rr_entry *le, *le2;
struct ibuf *buf;
struct iface *iface;
struct in_addr addr;
u_int32_t netid, netmask;
u_int8_t metric, netid_len, prefixlen;
if (xiface == NULL) {
iface = nbr->iface;
addr = nbr->addr;
} else {
iface = xiface;
inet_pton(AF_INET, AllDVMRPRouters, &addr);
}
while (!TAILQ_EMPTY(rr_list)) {
if ((buf = ibuf_open(iface->mtu - sizeof(struct ip))) == NULL)
fatal("rr_list_send");
prefixlen = 0;
while (((le = TAILQ_FIRST(rr_list)) != NULL) &&
(ibuf_size(buf) < 1000)) {
netmask = le->re->mask.s_addr;
if (prefixlen != mask2prefixlen(netmask)) {
prefixlen = mask2prefixlen(netmask);
netmask = ntohl(netmask) << 8;
netmask = htonl(netmask);
ibuf_add(buf, &netmask, 3);
}
netid_len = PREFIX_SIZE(prefixlen);
netid = le->re->net.s_addr;
ibuf_add(buf, &netid, netid_len);
if (iface->ifindex == le->re->ifindex)
metric = le->re->metric + INFINITY_METRIC;
else
metric = le->re->metric;
le2 = TAILQ_NEXT(le, entry);
if (le2 != NULL) {
if (mask2prefixlen(le2->re->mask.s_addr) !=
prefixlen)
metric = metric | LAST_MASK;
} else {
metric = metric | LAST_MASK;
}
ibuf_add(buf, &metric, sizeof(metric));
TAILQ_REMOVE(rr_list, le, entry);
rr_list_remove(le->re);
free(le);
}
send_report(iface, addr, ibuf_data(buf), ibuf_size(buf));
ibuf_free(buf);
}
}