bird/proto/ospf/lsalib.c
Ondrej Zajicek 30d09eb96e OSPF: Fixes validation of LSA checksums
Prior to this patch, BIRD validates the OSPF LSA checksum by calculating
a new checksum and comparing it with the checksum in the header. Due to
the specifics of the Fletcher checksum used in OSPF, this is not
necessarily correct as the checkbytes in the header may be calculated via
a different means and end up with a different value that is nonetheless
still correct.

The documented means of validating the checksum as specified in RFC 905
B.4 is to calculate c0 and c1 from the unchanged contents of the packet,
which must result in a zero value to be considered valid.

Thanks to Chris Boot for the patch.
2015-04-28 13:45:44 +02:00

711 lines
15 KiB
C

/*
* BIRD -- OSPF
*
* (c) 1999--2004 Ondrej Filip <feela@network.cz>
* (c) 2009--2014 Ondrej Zajicek <santiago@crfreenet.org>
* (c) 2009--2014 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "ospf.h"
#ifndef CPU_BIG_ENDIAN
void
lsa_hton_hdr(struct ospf_lsa_header *h, struct ospf_lsa_header *n)
{
n->age = htons(h->age);
n->type_raw = htons(h->type_raw);
n->id = htonl(h->id);
n->rt = htonl(h->rt);
n->sn = htonl(h->sn);
n->checksum = htons(h->checksum);
n->length = htons(h->length);
}
void
lsa_ntoh_hdr(struct ospf_lsa_header *n, struct ospf_lsa_header *h)
{
h->age = ntohs(n->age);
h->type_raw = ntohs(n->type_raw);
h->id = ntohl(n->id);
h->rt = ntohl(n->rt);
h->sn = ntohl(n->sn);
h->checksum = ntohs(n->checksum);
h->length = ntohs(n->length);
}
void
lsa_hton_body(void *h, void *n, u16 len)
{
u32 *hid = h;
u32 *nid = n;
uint i;
for (i = 0; i < (len / sizeof(u32)); i++)
nid[i] = htonl(hid[i]);
}
void
lsa_ntoh_body(void *n, void *h, u16 len)
{
u32 *nid = n;
u32 *hid = h;
uint i;
for (i = 0; i < (len / sizeof(u32)); i++)
hid[i] = ntohl(nid[i]);
}
#endif /* little endian */
int
lsa_flooding_allowed(u32 type, u32 domain, struct ospf_iface *ifa)
{
/* Handle inactive vlinks */
if (ifa->state == OSPF_IS_DOWN)
return 0;
/* 4.5.2 (Case 2) */
switch (LSA_SCOPE(type))
{
case LSA_SCOPE_LINK:
return ifa->iface_id == domain;
case LSA_SCOPE_AREA:
return ifa->oa->areaid == domain;
case LSA_SCOPE_AS:
if (ifa->type == OSPF_IT_VLINK)
return 0;
if (!oa_is_ext(ifa->oa))
return 0;
return 1;
default:
log(L_ERR "OSPF: LSA with invalid scope");
return 0;
}
}
static int
unknown_lsa_type(u32 type)
{
switch (type)
{
case LSA_T_RT:
case LSA_T_NET:
case LSA_T_SUM_NET:
case LSA_T_SUM_RT:
case LSA_T_EXT:
case LSA_T_NSSA:
case LSA_T_LINK:
case LSA_T_PREFIX:
return 0;
default:
return 1;
}
}
#define LSA_V2_TMAX 8
static const u16 lsa_v2_types[LSA_V2_TMAX] =
{0, LSA_T_RT, LSA_T_NET, LSA_T_SUM_NET, LSA_T_SUM_RT, LSA_T_EXT, 0, LSA_T_NSSA};
void
lsa_get_type_domain_(u32 itype, struct ospf_iface *ifa, u32 *otype, u32 *domain)
{
if (ospf_is_v2(ifa->oa->po))
{
itype = itype & LSA_T_V2_MASK;
itype = (itype < LSA_V2_TMAX) ? lsa_v2_types[itype] : 0;
}
else
{
/* For unkown LSAs without U-bit change scope to LSA_SCOPE_LINK */
if (unknown_lsa_type(itype) && !(itype & LSA_UBIT))
itype = itype & ~LSA_SCOPE_MASK;
}
*otype = itype;
switch (LSA_SCOPE(itype))
{
case LSA_SCOPE_LINK:
*domain = ifa->iface_id;
return;
case LSA_SCOPE_AREA:
*domain = ifa->oa->areaid;
return;
case LSA_SCOPE_AS:
default:
*domain = 0;
return;
}
}
/*
void
buf_dump(const char *hdr, const byte *buf, int blen)
{
char b2[1024];
char *bp;
int first = 1;
int i;
const char *lhdr = hdr;
bp = b2;
for(i = 0; i < blen; i++)
{
if ((i > 0) && ((i % 16) == 0))
{
*bp = 0;
log(L_WARN "%s\t%s", lhdr, b2);
lhdr = "";
bp = b2;
}
bp += snprintf(bp, 1022, "%02x ", buf[i]);
}
*bp = 0;
log(L_WARN "%s\t%s", lhdr, b2);
}
*/
#define MODX 4102 /* larges signed value without overflow */
/* Fletcher Checksum -- Refer to RFC1008. */
#define MODX 4102
#define LSA_CHECKSUM_OFFSET 15
/* FIXME This is VERY uneficient, I have huge endianity problems */
void
lsasum_calculate(struct ospf_lsa_header *h, void *body)
{
u16 length = h->length;
// log(L_WARN "Checksum %R %R %d start (len %d)", h->id, h->rt, h->type, length);
lsa_hton_hdr(h, h);
lsa_hton_body1(body, length - sizeof(struct ospf_lsa_header));
/*
char buf[1024];
memcpy(buf, h, sizeof(struct ospf_lsa_header));
memcpy(buf + sizeof(struct ospf_lsa_header), body, length - sizeof(struct ospf_lsa_header));
buf_dump("CALC", buf, length);
*/
(void) lsasum_check(h, body, 1);
// log(L_WARN "Checksum result %4x", h->checksum);
lsa_ntoh_hdr(h, h);
lsa_ntoh_body1(body, length - sizeof(struct ospf_lsa_header));
}
/*
* Calculates the Fletcher checksum of an OSPF LSA.
*
* If 'update' is non-zero, the checkbytes (X and Y in RFC905) are calculated
* and the checksum field in the header is updated. The return value is the
* checksum as placed in the header (in network byte order).
*
* If 'update' is zero, only C0 and C1 are calculated and the header is kept
* intact. The return value is a combination of C0 and C1; if the return value
* is exactly zero the checksum is considered valid, any non-zero value is
* invalid.
*
* Note that this function expects the input LSA to be in network byte order.
*/
u16
lsasum_check(struct ospf_lsa_header *h, void *body, int update)
{
u8 *sp, *ep, *p, *q, *b;
int c0 = 0, c1 = 0;
int x, y;
u16 length;
b = body;
sp = (char *) h;
sp += 2; /* Skip Age field */
length = ntohs(h->length) - 2;
if (update) h->checksum = 0;
for (ep = sp + length; sp < ep; sp = q)
{ /* Actually MODX is very large, do we need the for-cyclus? */
q = sp + MODX;
if (q > ep)
q = ep;
for (p = sp; p < q; p++)
{
/*
* I count with bytes from header and than from body
* but if there is no body, it's appended to header
* (probably checksum in update receiving) and I go on
* after header
*/
if ((b == NULL) || (p < (u8 *) (h + 1)))
{
c0 += *p;
}
else
{
c0 += *(b + (p - (u8 *) (h + 1)));
}
c1 += c0;
}
c0 %= 255;
c1 %= 255;
}
if (!update) {
/*
* When testing the checksum, we don't need to calculate x and y. The
* checksum passes if c0 and c1 are both 0.
*/
return (c0 << 8) | (c1 & 0xff);
}
x = (int)((length - LSA_CHECKSUM_OFFSET) * c0 - c1) % 255;
if (x <= 0)
x += 255;
y = 510 - c0 - x;
if (y > 255)
y -= 255;
((u8 *) & h->checksum)[0] = x;
((u8 *) & h->checksum)[1] = y;
return h->checksum;
}
int
lsa_comp(struct ospf_lsa_header *l1, struct ospf_lsa_header *l2)
/* Return codes from point of view of l1 */
{
u32 sn1, sn2;
sn1 = l1->sn - LSA_INITSEQNO + 1;
sn2 = l2->sn - LSA_INITSEQNO + 1;
if (sn1 > sn2)
return CMP_NEWER;
if (sn1 < sn2)
return CMP_OLDER;
if (l1->checksum != l2->checksum)
return l1->checksum < l2->checksum ? CMP_OLDER : CMP_NEWER;
if ((l1->age == LSA_MAXAGE) && (l2->age != LSA_MAXAGE))
return CMP_NEWER;
if ((l2->age == LSA_MAXAGE) && (l1->age != LSA_MAXAGE))
return CMP_OLDER;
if (ABS(l1->age - l2->age) > LSA_MAXAGEDIFF)
return l1->age < l2->age ? CMP_NEWER : CMP_OLDER;
return CMP_SAME;
}
static inline int
lsa_walk_rt2(struct ospf_lsa_rt_walk *rt)
{
if (rt->buf >= rt->bufend)
return 0;
struct ospf_lsa_rt2_link *l = rt->buf;
rt->buf += sizeof(struct ospf_lsa_rt2_link) + l->no_tos * sizeof(struct ospf_lsa_rt2_tos);
rt->type = l->type;
rt->metric = l->metric;
rt->id = l->id;
rt->data = l->data;
return 1;
}
static inline int
lsa_walk_rt3(struct ospf_lsa_rt_walk *rt)
{
while (rt->buf >= rt->bufend)
{
rt->en = ospf_hash_find_rt3_next(rt->en);
if (!rt->en)
return 0;
rt->buf = rt->en->lsa_body;
rt->bufend = rt->buf + rt->en->lsa.length - sizeof(struct ospf_lsa_header);
rt->buf += sizeof(struct ospf_lsa_rt);
}
struct ospf_lsa_rt3_link *l = rt->buf;
rt->buf += sizeof(struct ospf_lsa_rt3_link);
rt->type = l->type;
rt->metric = l->metric;
rt->lif = l->lif;
rt->nif = l->nif;
rt->id = l->id;
return 1;
}
void
lsa_walk_rt_init(struct ospf_proto *p, struct top_hash_entry *act, struct ospf_lsa_rt_walk *rt)
{
rt->ospf2 = ospf_is_v2(p);
rt->id = rt->data = rt->lif = rt->nif = 0;
if (rt->ospf2)
rt->en = act;
else
rt->en = ospf_hash_find_rt3_first(p->gr, act->domain, act->lsa.rt);
rt->buf = rt->en->lsa_body;
rt->bufend = rt->buf + rt->en->lsa.length - sizeof(struct ospf_lsa_header);
rt->buf += sizeof(struct ospf_lsa_rt);
}
int
lsa_walk_rt(struct ospf_lsa_rt_walk *rt)
{
return rt->ospf2 ? lsa_walk_rt2(rt) : lsa_walk_rt3(rt);
}
void
lsa_parse_sum_net(struct top_hash_entry *en, int ospf2, ip_addr *ip, int *pxlen, u8 *pxopts, u32 *metric)
{
if (ospf2)
{
struct ospf_lsa_sum2 *ls = en->lsa_body;
*ip = ipa_from_u32(en->lsa.id & ls->netmask);
*pxlen = u32_masklen(ls->netmask);
*pxopts = 0;
*metric = ls->metric & LSA_METRIC_MASK;
}
else
{
struct ospf_lsa_sum3_net *ls = en->lsa_body;
u16 rest;
lsa_get_ipv6_prefix(ls->prefix, ip, pxlen, pxopts, &rest);
*metric = ls->metric & LSA_METRIC_MASK;
}
}
void
lsa_parse_sum_rt(struct top_hash_entry *en, int ospf2, u32 *drid, u32 *metric, u32 *options)
{
if (ospf2)
{
struct ospf_lsa_sum2 *ls = en->lsa_body;
*drid = en->lsa.id;
*metric = ls->metric & LSA_METRIC_MASK;
*options = 0;
}
else
{
struct ospf_lsa_sum3_rt *ls = en->lsa_body;
*drid = ls->drid;
*metric = ls->metric & LSA_METRIC_MASK;
*options = ls->options & LSA_OPTIONS_MASK;
}
}
void
lsa_parse_ext(struct top_hash_entry *en, int ospf2, struct ospf_lsa_ext_local *rt)
{
if (ospf2)
{
struct ospf_lsa_ext2 *ext = en->lsa_body;
rt->ip = ipa_from_u32(en->lsa.id & ext->netmask);
rt->pxlen = u32_masklen(ext->netmask);
rt->pxopts = 0;
rt->metric = ext->metric & LSA_METRIC_MASK;
rt->ebit = ext->metric & LSA_EXT2_EBIT;
rt->fbit = ext->fwaddr;
rt->fwaddr = ipa_from_u32(ext->fwaddr);
rt->tag = ext->tag;
rt->propagate = lsa_get_options(&en->lsa) & OPT_P;
}
else
{
struct ospf_lsa_ext3 *ext = en->lsa_body;
u16 rest;
u32 *buf = lsa_get_ipv6_prefix(ext->rest, &rt->ip, &rt->pxlen, &rt->pxopts, &rest);
rt->metric = ext->metric & LSA_METRIC_MASK;
rt->ebit = ext->metric & LSA_EXT3_EBIT;
rt->fbit = ext->metric & LSA_EXT3_FBIT;
if (rt->fbit)
buf = lsa_get_ipv6_addr(buf, &rt->fwaddr);
else
rt->fwaddr = IPA_NONE;
rt->tag = (ext->metric & LSA_EXT3_TBIT) ? *buf++ : 0;
rt->propagate = rt->pxopts & OPT_PX_P;
}
}
#define HDRLEN sizeof(struct ospf_lsa_header)
static int
lsa_validate_rt2(struct ospf_lsa_header *lsa, struct ospf_lsa_rt *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_rt)))
return 0;
uint i = 0;
void *buf = body;
void *bufend = buf + lsa->length - HDRLEN;
buf += sizeof(struct ospf_lsa_rt);
while (buf < bufend)
{
struct ospf_lsa_rt2_link *l = buf;
buf += sizeof(struct ospf_lsa_rt2_link) + l->no_tos * sizeof(struct ospf_lsa_rt2_tos);
i++;
if (buf > bufend)
return 0;
if (!((l->type == LSART_PTP) ||
(l->type == LSART_NET) ||
(l->type == LSART_STUB) ||
(l->type == LSART_VLNK)))
return 0;
}
if ((body->options & LSA_RT2_LINKS) != i)
return 0;
return 1;
}
static int
lsa_validate_rt3(struct ospf_lsa_header *lsa, struct ospf_lsa_rt *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_rt)))
return 0;
void *buf = body;
void *bufend = buf + lsa->length - HDRLEN;
buf += sizeof(struct ospf_lsa_rt);
while (buf < bufend)
{
struct ospf_lsa_rt3_link *l = buf;
buf += sizeof(struct ospf_lsa_rt3_link);
if (buf > bufend)
return 0;
if (!((l->type == LSART_PTP) ||
(l->type == LSART_NET) ||
(l->type == LSART_VLNK)))
return 0;
}
return 1;
}
static int
lsa_validate_net(struct ospf_lsa_header *lsa, struct ospf_lsa_net *body UNUSED)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_net)))
return 0;
return 1;
}
static int
lsa_validate_sum2(struct ospf_lsa_header *lsa, struct ospf_lsa_sum2 *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_sum2)))
return 0;
/* First field should have TOS = 0, we ignore other TOS fields */
if ((body->metric & LSA_SUM2_TOS) != 0)
return 0;
return 1;
}
static inline int
pxlen(u32 *buf)
{
return *buf >> 24;
}
static int
lsa_validate_sum3_net(struct ospf_lsa_header *lsa, struct ospf_lsa_sum3_net *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_sum3_net) + 4))
return 0;
u8 pxl = pxlen(body->prefix);
if (pxl > MAX_PREFIX_LENGTH)
return 0;
if (lsa->length != (HDRLEN + sizeof(struct ospf_lsa_sum3_net) +
IPV6_PREFIX_SPACE(pxl)))
return 0;
return 1;
}
static int
lsa_validate_sum3_rt(struct ospf_lsa_header *lsa, struct ospf_lsa_sum3_rt *body)
{
if (lsa->length != (HDRLEN + sizeof(struct ospf_lsa_sum3_rt)))
return 0;
return 1;
}
static int
lsa_validate_ext2(struct ospf_lsa_header *lsa, struct ospf_lsa_ext2 *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_ext2)))
return 0;
/* First field should have TOS = 0, we ignore other TOS fields */
if ((body->metric & LSA_EXT2_TOS) != 0)
return 0;
return 1;
}
static int
lsa_validate_ext3(struct ospf_lsa_header *lsa, struct ospf_lsa_ext3 *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_ext3) + 4))
return 0;
u8 pxl = pxlen(body->rest);
if (pxl > MAX_PREFIX_LENGTH)
return 0;
int len = IPV6_PREFIX_SPACE(pxl);
if (body->metric & LSA_EXT3_FBIT) // forwardinf address
len += 16;
if (body->metric & LSA_EXT3_TBIT) // route tag
len += 4;
if (*body->rest & 0xFFFF) // referenced LS type field
len += 4;
if (lsa->length != (HDRLEN + sizeof(struct ospf_lsa_ext3) + len))
return 0;
return 1;
}
static int
lsa_validate_pxlist(struct ospf_lsa_header *lsa, u32 pxcount, uint offset, u8 *pbuf)
{
uint bound = lsa->length - HDRLEN - 4;
u32 i;
for (i = 0; i < pxcount; i++)
{
if (offset > bound)
return 0;
u8 pxl = pxlen((u32 *) (pbuf + offset));
if (pxl > MAX_PREFIX_LENGTH)
return 0;
offset += IPV6_PREFIX_SPACE(pxl);
}
if (lsa->length != (HDRLEN + offset))
return 0;
return 1;
}
static int
lsa_validate_link(struct ospf_lsa_header *lsa, struct ospf_lsa_link *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_link)))
return 0;
return lsa_validate_pxlist(lsa, body->pxcount, sizeof(struct ospf_lsa_link), (u8 *) body);
}
static int
lsa_validate_prefix(struct ospf_lsa_header *lsa, struct ospf_lsa_prefix *body)
{
if (lsa->length < (HDRLEN + sizeof(struct ospf_lsa_prefix)))
return 0;
return lsa_validate_pxlist(lsa, body->pxcount, sizeof(struct ospf_lsa_prefix), (u8 *) body);
}
/**
* lsa_validate - check whether given LSA is valid
* @lsa: LSA header
* @body: pointer to LSA body
*
* Checks internal structure of given LSA body (minimal length,
* consistency). Returns true if valid.
*/
int
lsa_validate(struct ospf_lsa_header *lsa, u32 lsa_type, int ospf2, void *body)
{
if (ospf2)
{
switch (lsa_type)
{
case LSA_T_RT:
return lsa_validate_rt2(lsa, body);
case LSA_T_NET:
return lsa_validate_net(lsa, body);
case LSA_T_SUM_NET:
return lsa_validate_sum2(lsa, body);
case LSA_T_SUM_RT:
return lsa_validate_sum2(lsa, body);
case LSA_T_EXT:
case LSA_T_NSSA:
return lsa_validate_ext2(lsa, body);
default:
return 0; /* Should not happen, unknown LSAs are already rejected */
}
}
else
{
switch (lsa_type)
{
case LSA_T_RT:
return lsa_validate_rt3(lsa, body);
case LSA_T_NET:
return lsa_validate_net(lsa, body);
case LSA_T_SUM_NET:
return lsa_validate_sum3_net(lsa, body);
case LSA_T_SUM_RT:
return lsa_validate_sum3_rt(lsa, body);
case LSA_T_EXT:
case LSA_T_NSSA:
return lsa_validate_ext3(lsa, body);
case LSA_T_LINK:
return lsa_validate_link(lsa, body);
case LSA_T_PREFIX:
return lsa_validate_prefix(lsa, body);
default:
return 1; /* Unknown LSAs are OK in OSPFv3 */
}
}
}