From fed1bceb2cf6d424396347921cfc9775c9a8af9b Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Wed, 29 Aug 2012 12:35:28 +0200 Subject: [PATCH] Some preliminary IS-IS commit. --- conf/conf.h | 8 + conf/confbase.Y | 7 + configure.in | 2 +- nest/proto.c | 3 + nest/protocol.h | 2 +- proto/isis/Makefile | 5 + proto/isis/config.Y | 187 +++++++++++++++ proto/isis/ifaces.c | 354 ++++++++++++++++++++++++++++ proto/isis/isis.c | 124 ++++++++++ proto/isis/isis.h | 206 ++++++++++++++++ proto/isis/lsdb.c | 89 +++++++ proto/isis/packets.c | 541 +++++++++++++++++++++++++++++++++++++++++++ sysdep/autoconf.h.in | 1 + 13 files changed, 1527 insertions(+), 2 deletions(-) create mode 100644 proto/isis/Makefile create mode 100644 proto/isis/config.Y create mode 100644 proto/isis/ifaces.c create mode 100644 proto/isis/isis.c create mode 100644 proto/isis/isis.h create mode 100644 proto/isis/lsdb.c create mode 100644 proto/isis/packets.c diff --git a/conf/conf.h b/conf/conf.h index c76832b6..9e9dc8c5 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -72,6 +72,14 @@ void config_add_obstacle(struct config *); void config_del_obstacle(struct config *); void order_shutdown(void); +static inline void +cf_range(const char *opt, int val, int min, int max) +{ + if ((val < min) || (val > max)) + cf_error("%s must be in range %d-%d", opt, min, max); +} + + #define CONF_DONE 0 #define CONF_PROGRESS 1 #define CONF_QUEUED 2 diff --git a/conf/confbase.Y b/conf/confbase.Y index dcb0719f..df5a9a4d 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -26,6 +26,13 @@ CF_HDR CF_DEFINES +static void +check_u8(unsigned val) +{ + if (val > 0xFF) + cf_error("Value %d out of range (0-255)", val); +} + static void check_u16(unsigned val) { diff --git a/configure.in b/configure.in index 54993dfc..32225de9 100644 --- a/configure.in +++ b/configure.in @@ -51,7 +51,7 @@ if test "$enable_ipv6" = yes ; then else ip=ipv4 SUFFIX="" - all_protocols=bgp,ospf,pipe,rip,static + all_protocols=bgp,ospf,isis,pipe,rip,static fi if test "$given_suffix" = yes ; then diff --git a/nest/proto.c b/nest/proto.c index 53d3f1a2..5ae84f47 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -701,6 +701,9 @@ protos_build(void) #ifdef CONFIG_OSPF proto_build(&proto_ospf); #endif +#ifdef CONFIG_ISIS + proto_build(&proto_isis); +#endif #ifdef CONFIG_PIPE proto_build(&proto_pipe); #endif diff --git a/nest/protocol.h b/nest/protocol.h index 11fcb164..fd77cefe 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -75,7 +75,7 @@ void protos_dump_all(void); extern struct protocol proto_device, proto_radv, proto_rip, proto_static, - proto_ospf, proto_pipe, proto_bgp; + proto_ospf, proto_isis, proto_pipe, proto_bgp; /* * Routing Protocol Instance diff --git a/proto/isis/Makefile b/proto/isis/Makefile new file mode 100644 index 00000000..d022e9b7 --- /dev/null +++ b/proto/isis/Makefile @@ -0,0 +1,5 @@ +source=isis.c ifaces.c lsdb.c packets.c +root-rel=../../ +dir-name=proto/isis + +include ../../Rules diff --git a/proto/isis/config.Y b/proto/isis/config.Y new file mode 100644 index 00000000..221f04a1 --- /dev/null +++ b/proto/isis/config.Y @@ -0,0 +1,187 @@ +/* + * BIRD -- IS-IS Configuration + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#include "proto/isis/isis.h" + +CF_DEFINES + +#define ISIS_CFG ((struct isis_config *) this_proto) +#define ISIS_IFACE ((struct isis_iface_config *) this_ipatt) + +static byte area_id_buffer[13]; +static byte *area_id_bpos; + +CF_DECLS + +CF_KEYWORDS(AREA, BCAST, BROADCAST, BUFFER, CSNP, HELLO, HOLD, ID, INTERFACE, ISIS, LEVEL, LIFETIME, + LSP, METRIC, MULT, PASSIVE, POINTOPOINT, PRIORITY, PSNP, PTP, RETRANSMIT, RX, SIZE, SYSTEM, + TX, TYPE) + + +%type isis_level + +CF_GRAMMAR + +CF_ADDTO(proto, isis_proto) + +isis_proto_start: proto_start ISIS +{ + this_proto = proto_config_new(&proto_isis, sizeof(struct isis_config), $1); + init_list(&ISIS_CFG->patt_list); + ISIS_CFG->lsp_lifetime = ISIS_DEFAULT_LSP_LIFETIME; + ISIS_CFG->rx_buffer_size = ISIS_DEFAULT_RX_BUFFER_SIZE; + ISIS_CFG->tx_buffer_size = ISIS_DEFAULT_TX_BUFFER_SIZE; +}; + +isis_proto_item: + proto_item + | INTERFACE isis_iface + | AREA ID isis_area_id + | SYSTEM ID isis_system_id + | LSP LIFETIME expr { ISIS_CFG->lsp_lifetime = $3; cf_range("LSP lifetime", $3, 300, 65535); } + | RX BUFFER SIZE expr { ISIS_CFG->rx_buffer_size = $4; cf_range("RX buffer size", $4, 256, 65535); } + | TX BUFFER SIZE expr { ISIS_CFG->tx_buffer_size = $4; cf_range("TX buffer size", $4, 256, 65535); } + ; + +isis_proto_finish: +{ + struct isis_config *cf = ISIS_CFG; + + if (! cf->areas[0]) + cf_error("Missing area ID"); + + if (cf->rx_buffer_size < cf->tx_buffer_size) + cf_error("RX buffer size must be greater than TX buffer size"); +} + +isis_proto_opts: + /* empty */ + | isis_proto_opts isis_proto_item ';' + ; + +isis_proto: + isis_proto_start proto_name '{' isis_proto_opts '}'; + + +isis_iface_start: +{ + this_ipatt = cfg_allocz(sizeof(struct isis_iface_config)); + add_tail(&ISIS_CFG->patt_list, NODE this_ipatt); + + ISIS_IFACE->levels[ISIS_L1] = ISIS_DEFAULT_LEVEL_1; + ISIS_IFACE->levels[ISIS_L2] = ISIS_DEFAULT_LEVEL_2; + ISIS_IFACE->metric = ISIS_DEFAULT_METRIC; + ISIS_IFACE->priority = ISIS_DEFAULT_PRIORITY; + + ISIS_IFACE->hello_int = ISIS_DEFAULT_HELLO_INT; + ISIS_IFACE->hold_int = ISIS_DEFAULT_HOLD_INT; + ISIS_IFACE->hold_mult = ISIS_DEFAULT_HOLD_MULT; + ISIS_IFACE->rxmt_int = ISIS_DEFAULT_RXMT_INT; + ISIS_IFACE->csnp_int = ISIS_DEFAULT_CSNP_INT; + ISIS_IFACE->psnp_int = ISIS_DEFAULT_PSNP_INT; +}; + +isis_level: expr { $$ = $1 - 1; if (($1 < 1) || ($1 > 2)) cf_error("Level must be 1 or 2"); } + +isis_iface_item: + LEVEL isis_level bool { ISIS_IFACE->levels[$2] = $3; } + | LEVEL isis_level PASSIVE { ISIS_IFACE->levels[$2] = ISIS_LEVEL_PASSIVE; } + | PASSIVE bool { ISIS_IFACE->passive = $2; } + + | TYPE BROADCAST { ISIS_IFACE->type = ISIS_IT_BCAST; } + | TYPE BCAST { ISIS_IFACE->type = ISIS_IT_BCAST; } + | TYPE POINTOPOINT { ISIS_IFACE->type = ISIS_IT_PTP; } + | TYPE PTP { ISIS_IFACE->type = ISIS_IT_PTP; } + + | METRIC expr { ISIS_IFACE->metric = $2; cf_range("Metric", $2, 1, 63); } + | PRIORITY expr { ISIS_IFACE->priority = $2; cf_range("Priority", $2, 1, 127); } + + | HELLO expr { ISIS_IFACE->hello_int = $2; cf_range("Hello interval", $2, 1, 65535); } + | HOLD expr { ISIS_IFACE->hold_int = $2; cf_range("Hold interval", $2, 3, 65535); } + | HOLD MULT expr { ISIS_IFACE->hold_mult = $3; cf_range("Hold multiplier", $3, 3, 255); } + | RETRANSMIT expr { ISIS_IFACE->rxmt_int = $2; cf_range("Retransmit interval", $2, 3, 65535); } + | CSNP expr { ISIS_IFACE->csnp_int = $2; cf_range("CSNP interval", $2, 1, 65535); } + | PSNP expr { ISIS_IFACE->psnp_int = $2; cf_range("PSNP interval", $2, 1, 65535); } + ; + +isis_iface_finish: +{ + struct isis_iface_config *ic = ISIS_IFACE; + + if (! ic->hold_int) + { + u32 hold_int = ic->hold_mult * (u32) ic->hello_int; + if (hold_int > 65535) + cf_error("Hello interval times Hold multiplier greater than 65535"); + ic->hold_int = hold_int; + } +}; + +isis_iface_opts: + /* empty */ + | isis_iface_opts isis_iface_item ';' + ; + +isis_iface_opt_list: + /* empty */ + | '{' isis_iface_opts '}' + ; + +isis_iface: + isis_iface_start iface_patt_list isis_iface_opt_list isis_iface_finish; + + + +isis_area_id_read: + NUM + { + check_u8($1); + if ($1 == 0) + cf_error("Area ID must not start with 0"); + area_id_bpos = area_id_buffer; + *area_id_bpos++ = $1; + } + | isis_area_id_read '-' NUM + { + check_u16($3); + if ((area_id_bpos + 2 - area_id_buffer) > sizeof(area_id_buffer)) + cf_error("Area ID too long"); + put_u16(area_id_bpos, $3); + area_id_bpos += 2; + } + ; + +isis_area_id: isis_area_id_read +{ + struct isis_area_id *area_id; + int i, blen; + + for (i = 0; i < ISIS_AREAS; i++) + if (! ISIS_CFG->areas[i]) + goto found; + + cf_error("Too many areas"); + +found: + blen = area_id_bpos - area_id_buffer; + area_id = cfg_allocz(1 + blen); + area_id->length = blen; + memcpy(area_id->body, area_id_buffer, blen); + ISIS_CFG->areas[i] = area_id; +} + +isis_system_id: NUM '-' NUM '-' NUM +{ + check_u16($1); check_u16($3); check_u16($5); + ISIS_CFG->system_id = (((u64) $1) << 48) | (((u64) $3) << 32) | (((u64) $5) << 16); +} + +CF_CODE + +CF_END diff --git a/proto/isis/ifaces.c b/proto/isis/ifaces.c new file mode 100644 index 00000000..9869dbad --- /dev/null +++ b/proto/isis/ifaces.c @@ -0,0 +1,354 @@ +/* + * BIRD -- IS-IS Interfaces and Neighbors + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +#include +#include "isis.h" + +static char* ev_name[] = { NULL, "Init", "Change", "RS" }; + +#ifdef XXX +static void +isis_timer(timer *tm) +{ + struct isis_iface *ifa = tm->data; + struct isis_proto *p = ifa->ra; + + ISIS_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name); + + isis_send_ra(ifa, 0); + + /* Update timer */ + ifa->last = now; + unsigned after = ifa->cf->min_ra_int; + after += random() % (ifa->cf->max_ra_int - ifa->cf->min_ra_int + 1); + + if (ifa->initial) + ifa->initial--; + + if (ifa->initial) + after = MIN(after, MAX_INITIAL_RTR_ADVERT_INTERVAL); + + tm_start(ifa->timer, after); +} + +void +isis_iface_notify(struct isis_iface *ifa, int event) +{ + struct isis_proto *p = ifa->p; + + if (!ifa->sk) + return; + + ISIS_TRACE(D_EVENTS, "Event %s on %s", ev_name[event], ifa->iface->name); + + switch (event) + { + case RA_EV_CHANGE: + ifa->plen = 0; + case RA_EV_INIT: + ifa->initial = MAX_INITIAL_RTR_ADVERTISEMENTS; + break; + + case RA_EV_RS: + break; + } + + /* Update timer */ + unsigned delta = now - ifa->last; + unsigned after = 0; + + if (delta < ifa->cf->min_delay) + after = ifa->cf->min_delay - delta; + + tm_start(ifa->timer, after); +} +#endif + +/* +isis_dr_election() +{ + + at least one up, all relevant neighbors and myself + + local system becomes or resign -> event lANLevel1/2DesignatedIntermediateSystemChange + becomes -> + set lan-id + originate new and purge old pseudonode LSP + + zmena dr -> zmenit lan-id + zmena lan-id -> zmena me LSP +} + */ + +static struct isis_iface * +isis_iface_find(struct isis_proto *p, struct iface *what) +{ + struct isis_iface *ifa; + + WALK_LIST(ifa, p->iface_list) + if (ifa->iface == what) + return ifa; + + return NULL; +} + +static void +iface_olock_hook(struct object_lock *lock) +{ + struct isis_iface *ifa = lock->data; + struct isis_proto *p = ifa->p; + + if (! isis_sk_open(ifa)) + { + log(L_ERR "%s: Socket open failed on interface %s", p->p.name, ifa->iface->name); + return; + } + + // XXX isis_iface_notify(ifa, RA_EV_INIT); +} + + +static void +l1_hello_timer_hook(timer *timer) +{ + struct isis_iface *ifa = (struct isis_iface *) timer->data; + + isis_send_lan_hello(ifa, ISIS_L1); +} + +static void +l2_hello_timer_hook(timer *timer) +{ + struct isis_iface *ifa = (struct isis_iface *) timer->data; + + isis_send_lan_hello(ifa, ISIS_L2); +} + +static void +ptp_hello_timer_hook(timer *timer) +{ + struct isis_iface *ifa = (struct isis_iface *) timer->data; + + isis_send_ptp_hello(ifa); +} + +static void +csnp_timer_hook(timer *timer) +{ + struct isis_iface *ifa = (struct isis_iface *) timer->data; + struct isis_proto *p = ifa->p; + struct isis_lsp *lsp; + int n; + + /* FIXME: CSNP rate limiting */ + + if (XXX) + { + n = 0; + lsp = isis_lsdb_first(p->lsdb[ISIS_L1], lsp, ifa); + while (lsp) + isis_send_csnp(ifa, ISIS_L1, &lsp, n++ == 0); + } + + if (XXX) + { + n = 0; + lsp = isis_lsdb_first(p->lsdb[ISIS_L2], lsp, ifa); + while (lsp) + isis_send_csnp(ifa, ISIS_L2, &lsp, n++ == 0); + } +} + +static void +psnp_timer_hook(timer *timer) +{ + struct isis_iface *ifa = (struct isis_iface *) timer->data; + struct isis_proto *p = ifa->p; + struct isis_lsp *lsp; + + if (XXX) + { + lsp = isis_lsdb_first_ssn(p->lsdb[ISIS_L1], lsp, ifa); + while (lsp) + isis_send_psnp(ifa, ISIS_L1, &lsp); + } + + if (XXX) + { + lsp = isis_lsdb_first_ssn(p->lsdb[ISIS_L2], lsp, ifa); + while (lsp) + isis_send_psnp(ifa, ISIS_L2, &lsp); + } +} + + +static void +isis_iface_new(struct isis_proto *p, struct iface *iface, struct isis_iface_config *cf) +{ + ISIS_TRACE(D_EVENTS, "Adding interface %s", iface->name); + + pool *pool = rp_new(p->p.pool, "ISIS Interface"); + struct isis_iface *ifa = mb_allocz(pool, sizeof(struct isis_iface)); + + add_tail(&p->iface_list, NODE ifa); + ifa->p = p; + ifa->cf = cf; + ifa->iface = iface; + + ifa->pool = pool; + init_list(&ifa->neigh_list); + + ifa->type = ifa->cf->type; + ifa->levels = ifa->cf->levels; + ifa->priority = ifa->cf->priority; + ifa->hello_int = ifa->cf->hello_int; + ifa->hold_int = ifa->cf->hold_int; + + if (ifa->type == ISIS_IT_PASSIVE) + return; + + ifa->hello_timer = tm_new_set(pool, hello_timer_hook, ifa, xxx, xxx); + + struct object_lock *lock = olock_new(pool); + lock->addr = IPA_NONE; + lock->type = OBJLOCK_IP; + lock->port = ISIS_PROTO; + lock->iface = iface; + lock->data = ifa; + lock->hook = iface_olock_hook; + ifa->lock = lock; + + olock_acquire(lock); +} + +/* +static inline void +isis_iface_shutdown(struct isis_iface *ifa) +{ + if (ifa->sk) + isis_send_ra(ifa, 1); +} +*/ + +static void +isis_iface_remove(struct isis_iface *ifa) +{ + struct isis_proto *p = ifa->p; + + ISIS_TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name); + + // XXX isis_iface_sm(ifa, ISM_DOWN); + rem_node(NODE ifa); + rfree(ifa->pool); +} + +void +isis_if_notify(struct proto *pp, unsigned flags, struct iface *iface) +{ + struct isis_proto *p = (struct isis_proto *) pp; + struct isis_config *cf = (struct isis_config *) (pp->cf); + + if (iface->flags & IF_IGNORE) + return; + + if (flags & IF_CHANGE_UP) + { + struct isis_iface_config *ic = (struct isis_iface_config *) + iface_patt_find(&cf->patt_list, iface, NULL); + + if (ic) + isis_iface_new(p, iface, ic); + + return; + } + + struct isis_iface *ifa = isis_iface_find(p, iface); + if (!ifa) + return; + + if (flags & IF_CHANGE_DOWN) + { + isis_iface_remove(ifa); + return; + } + + if ((flags & IF_CHANGE_LINK) && (iface->flags & IF_LINK_UP)) + isis_iface_notify(ifa, RA_EV_INIT); +} + +/* +void +isis_ifa_notify(struct proto *pp, unsigned flags, struct ifa *a) +{ + struct isis_proto *p = (struct isis_proto *) pp; + + if (a->flags & IA_SECONDARY) + return; + + if (a->scope <= SCOPE_LINK) + return; + + struct isis_iface *ifa = isis_iface_find(ra, a->iface); + + if (ifa) + isis_iface_notify(ifa, RA_EV_CHANGE); +} + +*/ + + + + +static void +hold_timer_hook(timer *timer) +{ + struct isis_neighbor *n = (struct isis_neighbor *) timer->data; + struct isis_iface *ifa = n->ifa; + struct isis_proto *p = ifa->p; + + // xxx ISIS_TRACE(D_EVENTS, "Hold timer expired for neighbor %I", n->ip); + isis_neighbor_remove(n); +} + + +struct isis_neighbor * +isis_neighbor_add(struct isis_iface *ifa) +{ + struct isis_proto *p = ifa->p; + struct isis_neighbor *n = mb_allocz(ifa->pool, sizeof(struct isis_neighbor)); + + add_tail(&ifa->neigh_list, NODE n); + n->ifa = ifa; + + n->hold_timer = tm_new_set(ifa->pool, hold_timer_hook, n, xxx, xxx); + + return (n); +} + +void +isis_neighbor_remove(struct isis_neighbor *n) +{ + struct isis_iface *ifa = n->ifa; + struct isis_proto *p = ifa->p; + + // xxx ISIS_TRACE(D_EVENTS, "Removing neigbor"); + + rem_node(NODE n); + rfree(n->hold_timer); +} + +/* + new: + t neighbourSystemType - podle typu paketu + holdingTimer, priorityOfNeighbour, neighbour-SystemID and areaAddressesOfNeighbour - podle obsahu + mac_addr + state -> init + checknout my sysID in list -> up + + + */ diff --git a/proto/isis/isis.c b/proto/isis/isis.c new file mode 100644 index 00000000..e98b7cbc --- /dev/null +++ b/proto/isis/isis.c @@ -0,0 +1,124 @@ +/* + * BIRD -- IS-IS + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +#include +#include "isis.h" + +/** + * DOC: Intermediate System to Intermediate System (IS-IS) + * + * Intermediate System to Intermediate System + * intra-domain routeing information exchange protocol + * + * XXXX + * + * Supported standards: + * - ISO 10589 - main IS-IS standard + * - RFC xxxx - + */ + + +static struct proto * +isis_init(struct proto_config *c) +{ + struct proto *pp = proto_new(c, sizeof(struct isis_proto)); + + pp->if_notify = isis_if_notify; + pp->ifa_notify = isis_ifa_notify; + return pp; +} + +static int +isis_start(struct proto *pp) +{ + struct isis_proto *p = (struct isis_proto *) pp; + // struct isis_config *cf = (struct isis_config *) (pp->cf); + + init_list(&(p->iface_list)); + + return PS_UP; +} + +static int +isis_shutdown(struct proto *pp) +{ + struct isis_proto *p = (struct isis_proto *) pp; + + struct isis_iface *ifa; + WALK_LIST(ifa, p->iface_list) + isis_iface_shutdown(ifa); + + return PS_DOWN; +} + +#ifdef XXX +static int +isis_reconfigure(struct proto *pp, struct proto_config *c) +{ + struct isis_proto *p = (struct isis_proto *) pp; + // struct isis_config *old = (struct isis_config *) (p->cf); + struct isis_config *new = (struct isis_config *) c; + + /* + * The question is why there is a reconfigure function for RAdv if + * it has almost none internal state so restarting the protocol + * would probably suffice. One small reason is that restarting the + * protocol would lead to sending a RA with Router Lifetime 0 + * causing nodes to temporary remove their default routes. + */ + + struct iface *iface; + WALK_LIST(iface, iface_list) + { + struct isis_iface *ifa = isis_iface_find(ra, iface); + struct isis_iface_config *ic = (struct isis_iface_config *) + iface_patt_find(&new->patt_list, iface, NULL); + + if (ifa && ic) + { + ifa->cf = ic; + + /* We cheat here - always notify the change even if there isn't + any. That would leads just to a few unnecessary RAs. */ + isis_iface_notify(ifa, RA_EV_CHANGE); + } + + if (ifa && !ic) + { + isis_iface_shutdown(ifa); + isis_iface_remove(ifa); + } + + if (!ifa && ic) + isis_iface_new(ra, iface, ic); + } + + return 1; +} + +static void +isis_copy_config(struct proto_config *dest, struct proto_config *src) +{ + struct isis_config *d = (struct isis_config *) dest; + struct isis_config *s = (struct isis_config *) src; + + /* We clean up patt_list, ifaces are non-sharable */ + init_list(&d->patt_list); +} +#endif + + +struct protocol isis_proto = { + .name = "IS-IS", + .template = "isis%d", + .init = isis_init, + .start = isis_start, + .shutdown = isis_shutdown, + // .reconfigure = isis_reconfigure, + // .copy_config = isis_copy_config +}; diff --git a/proto/isis/isis.h b/proto/isis/isis.h new file mode 100644 index 00000000..9a1c476a --- /dev/null +++ b/proto/isis/isis.h @@ -0,0 +1,206 @@ +/* + * BIRD -- Router Advertisement + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_ISIS_H_ +#define _BIRD_ISIS_H_ + +#include "nest/bird.h" + +#include "lib/ip.h" +#include "lib/lists.h" +#include "lib/socket.h" +#include "lib/timer.h" +#include "lib/resource.h" +#include "nest/protocol.h" +#include "nest/iface.h" +#include "nest/route.h" +#include "nest/cli.h" +#include "nest/locks.h" +#include "conf/conf.h" +#include "lib/string.h" + + +#define ISIS_PROTO 90 // XXX + +#define ISIS_LEVELS 2 +#define ISIS_L1 0 +#define ISIS_L2 1 + +#define ISIS_AREAS 3 + + +#define mac_addr ip_addr // XXX + +#define ETH_ALL_ISS ipa_from_u32(0xe0000005) /* 224.0.0.5 */ +#define ETH_ALL_L1_ISS ipa_from_u32(0xe0000005) /* 224.0.0.5 */ +#define ETH_ALL_L2_ISS ipa_from_u32(0xe0000005) /* 224.0.0.5 */ + + +struct isis_area_id +{ + byte length; + byte body[]; +}; + +struct isis_config +{ + struct proto_config c; + list patt_list; /* List of iface configs (struct isis_iface_config) */ + struct isis_area_id *areas[ISIS_AREAS]; + + u64 system_id; + + u16 lsp_lifetime; + u16 rx_buffer_size; + u16 tx_buffer_size; +}; + +#define ISIS_DEFAULT_LSP_LIFETIME 1200 +#define ISIS_DEFAULT_RX_BUFFER_SIZE 1492 +#define ISIS_DEFAULT_TX_BUFFER_SIZE 1492 + +struct isis_iface_config +{ + struct iface_patt i; + + u8 levels[ISIS_LEVELS]; + u8 passive; + u8 type; + u8 metric; + u8 priority; + + u16 hello_int; + u16 hold_int; + u16 hold_mult; + u16 rxmt_int; + u16 csnp_int; + u16 psnp_int; +}; + +#define ISIS_LEVEL_PASSIVE 2 + +#define ISIS_DEFAULT_LEVEL_1 1 +#define ISIS_DEFAULT_LEVEL_2 0 +#define ISIS_DEFAULT_METRIC 1 +#define ISIS_DEFAULT_PRIORITY 64 + +#define ISIS_DEFAULT_HELLO_INT 10 +#define ISIS_DEFAULT_HOLD_INT 0 +#define ISIS_DEFAULT_HOLD_MULT 3 +#define ISIS_DEFAULT_RXMT_INT 5 +#define ISIS_DEFAULT_CSNP_INT 10 +#define ISIS_DEFAULT_PSNP_INT 2 + + +struct isis_proto +{ + struct proto p; + list iface_list; /* List of active ifaces */ + + u64 system_id; + u16 lsp_max_age; + u16 lsp_refresh; + u16 buffer_size; +}; + +struct isis_iface +{ + node n; + struct isis_proto *p; + struct isis_iface_config *cf; /* Related config, must be updated in reconfigure */ + struct iface *iface; + + pool *pool; + struct object_lock *lock; + sock *sk; + list neigh_list; /* List of neigbours */ + + u8 type; + u8 levels; + u8 priority; + + u16 hello_int; + u16 hold_int; +}; + +#define ISIS_IT_UNDEF 0 +#define ISIS_IT_BCAST 1 +#define ISIS_IT_PTP 2 + + +struct isis_neighbor +{ + node n; + struct isis_iface *ifa; + mac_addr addr; + u64 id; + + timer *hold_timer; + + u8 levels; + u8 priority; +}; + +struct isis_lsdb +{ + pool *pool; + slab *slab; + list list; +}; + +struct isis_lsp_hdr +{ + u64 id; + u32 seqnum; + u16 lifetime; + u16 checksum; +} + +struct isis_lsp +{ + node n; + struct isis_lsp_hdr hdr; + void *body; + u16 blen; +}; + + + +#define RA_EV_INIT 1 /* Switch to initial mode */ +#define RA_EV_CHANGE 2 /* Change of options or prefixes */ +#define RA_EV_RS 3 /* Received RS */ + + + +#ifdef LOCAL_DEBUG +#define ISIS_FORCE_DEBUG 1 +#else +#define ISIS_FORCE_DEBUG 0 +#endif +#define ISIS_TRACE(flags, msg, args...) do { if ((p->p.debug & flags) || ISIS_FORCE_DEBUG) \ + log(L_TRACE "%s: " msg, p->p.name , ## args ); } while(0) + + +/* isis.c */ +void isis_iface_notify(struct isis_iface *ifa, int event); + +/* ifaces.c */ +void isis_if_notify(struct proto *pp, unsigned flags, struct iface *iface); +void isis_ifa_notify(struct proto *pp, unsigned flags, struct ifa *a); + +/* packets.c */ +void isis_send_lan_hello(struct isis_iface *ifa, int level); +void isis_send_ptp_hello(struct isis_iface *ifa); +void isis_send_lsp(struct isis_iface *ifa, struct lsp_entry *en); +void isis_send_csnp(struct isis_iface *ifa, int level); +void isis_send_psnp(struct isis_iface *ifa, int level); + +int isis_sk_open(struct isis_iface *ifa); + + + +#endif /* _BIRD_ISIS_H_ */ diff --git a/proto/isis/lsdb.c b/proto/isis/lsdb.c new file mode 100644 index 00000000..3fb6e696 --- /dev/null +++ b/proto/isis/lsdb.c @@ -0,0 +1,89 @@ +/* + * BIRD -- IS-IS LSP database + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +static inline int +isis_lsp_comp(struct isis_lsp_hdr *l1, struct isis_lsp_hdr *l2) +{ + if (l1->seqnum != l2->seqnum) + return (l1->seqnum > l2->seqnum) ? LSP_NEWER : LSP_OLDER; + + // XXX review + if (l1->checksum != l2->checksum) + return (l1->checksum > l2->checksum) ? LSP_NEWER : LSP_OLDER; + + return LSP_SAME; +} + +struct isis_lsp * +isis_get_lsp(struct isis_lsdb *db, xxx) +{ +} + + +void +isis_lsp_received(struct isis_lsdb *db, struct isis_iface *ifa, + struct isis_lsp_hdr *hdr, byte *body, int blen) +{ + struct isis_lsp *lsp = isis_get_lsp(db, hdr); + + int cmp = isis_lsp_comp(hdr, &lsp->hdr); + switch (cmp) + { + LSP_NEWER: + xxx(); + LSP_SAME: + isis_lsp_clear_srm(lsp, ifa); + isis_lsp_set_ack(lsp, ifa); + break; + + LSP_OLDER: + isis_lsp_set_srm(lsp, ifa); + isis_lsp_clear_ssn(lsp, ifa); + break; + } +} + +void +isis_snp_received(struct isis_lsdb *db, struct isis_iface *ifa, struct isis_lsp_hdr *hdr) +{ + struct isis_lsp *lsp = isis_get_lsp(db, hdr); + + int cmp = isis_lsp_comp(hdr, &lsp->hdr); + switch (cmp) + { + LSP_NEWER: + isis_lsp_set_ssn(lsp, ifa); + LSP_SAME: + isis_lsp_clear_ack(lsp, ifa); + break; + + LSP_OLDER: + isis_lsp_set_srm(lsp, ifa); + isis_lsp_clear_ssn(lsp, ifa); + break; + } + + isis_lsp_clear_csnp(lsp); +} + +struct isis_lsdb * +isis_lsdb_new(struct isis_proto *p) +{ + pool *pool = rp_new(p->p.pool, "ISIS LSP database"); + struct isis_lsdb *db = mb_allocz(pool, sizeof(struct isis_lsdb)); + + db->pool = pool; + db->slab = sl_new(pool, sizeof(struct lsp_entry)); + init_list(&db->list); +} + +void +isis_lsdb_free(struct isis_lsdb *db) +{ + rfree(db->pool); +} diff --git a/proto/isis/packets.c b/proto/isis/packets.c new file mode 100644 index 00000000..ee3d58e2 --- /dev/null +++ b/proto/isis/packets.c @@ -0,0 +1,541 @@ +/* + * BIRD -- IS-IS Packet Processing + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +#include +#include "isis.h" + + +/* + * IS-IS common packet header + * + * 1B Protocol ID, constant + * 1B Fixed header length depends on packet type + * 1B Version 1 fixed 1 + * 1B System ID length fixed 0 or 6 + * 1B Packet type 0xe0 bits reserved + * 1B Version 2 fixed 1 + * 1B Reserved ignored + * 1B Max area addrs fixed 0 or 3 + * + * + * IS-IS Hello header + * + * 8B Common header + * 1B Circuit type levels (1, 2 or 1+2), 0xfc bits reserved + * 6B System ID + * 2B Hold time + * 2B Packet length hdr + data + * LAN: + * 1B Priority 0x80 bit reserved + * 7B LAN ID for DR selection + * PTP: + * 1B Circuit ID we could ignore this (?) + * + * + * + * IS-IS LSP header + * + * 8B Common header + * 2B Packet length hdr + data + * 2B LSP lifetime + * 8B LSP ID + * 4B LSP sequence number + * 2B LSP checksum + * 1B LSP flags + * + * + * IS-IS CSNP header + * + * 8B Common header + * 2B Packet length hdr + data + * 7B System ID + * 8B Start LSP ID + * 8B End LSP ID + * + * + * IS-IS PSNP header + * + * 8B Common header + * 2B Packet length hdr + data + * 7B System ID + */ + + + +#define ISIS_PROTO_ID 0x83 + +#define ISIS_L1_HELLO 15 +#define ISIS_L2_HELLO 16 +#define ISIS_PTP_HELLO 17 +#define ISIS_L1_LSP 18 +#define ISIS_L2_LSP 20 +#define ISIS_L1_CSNP 24 +#define ISIS_L2_CSNP 25 +#define ISIS_L1_PSNP 26 +#define ISIS_L2_PSNP 27 + +#define ISIS_COMMON_HLEN 8 +#define ISIS_LHELLO_HLEN 27 +#define ISIS_PHELLO_HLEN 20 +#define ISIS_LSP_HLEN 27 +#define ISIS_CSNP_HLEN 33 +#define ISIS_PSNP_HLEN 17 + +#define ISIS_TYPE_MASK 0x1f + + +IntradomainRouteingPD + +static inline byte * isis_tx_buffer(struct isis_iface *ifa) { return ifa->sk->tbuf; } + +static void +isis_fill_hdr(byte *pkt, u8 pdu_type, u8 hdr_len) +{ + pkt[0] = ISIS_PROTO_ID; + pkt[1] = hdr_len; + pkt[2] = 1; // Version 1 + pkt[3] = 6; // System ID length + pkt[4] = pdu_type; + pkt[5] = 1; // Version 2 + pkt[6] = 0; // Reserved + pkt[7] = 3; // Max area addresses +} + +static inline byte * +put_lsp_hdr(byte *buf, struct isis_lsp_hdr *hdr) +{ + put_u16(buf+ 0, hdr->lifetime); + put_u64(buf+ 2, hdr->id); + put_u32(buf+10, hdr->seqnum); + put_u16(buf+14, hdr->checksum); + return buf+16; +} + +static byte * +isis_put_tlv_areas(struct isis_proto *p, byte *buf) +{ + byte *bp = buf + 2; + int i; + + for (i = 0; i < ISIS_AREAS; i++) + if (cf->areas[i]) + { + int alen = 1 + cf->areas[i]->length; + memcpy(bp, cf->areas[i], alen); + bp += alen; + } + + buf[0] = ISIS_TLV_AREAS; + buf[1] = bp - (buf + 2); + + return bp; +} + +static byte * +isis_put_tlv_protocols(struct isis_proto *p, byte *buf) +{ + buf[0] = ISIS_TLV_PROTOCOLS; + buf[1] = 1; + buf[2] = ISIS_NLPID_IPv4; + + return buf + 3; +} + +static byte * +isis_put_tlv_ip4_iface_addrs(struct isis_iface *ifa, byte *buf) +{ + struct ifa *a; + byte *bp = buf + 2; + int i; + + WALK_LIST(a, ifa->iface->addrs) + { + bp = ipa_put_addr(bp, a); + } + + buf[0] = ISIS_TLV_IP4_IFACE_ADDRS; + buf[1] = bp - (buf + 2); + + return bp; +} + + +void +isis_send_lan_hello(struct isis_iface *ifa, int level) +{ + struct isis_proto *p = ifa->p; + + byte *pkt = isis_tx_buffer(ifa); + isis_fill_hdr(pkt, !level ? ISIS_L1_HELLO : ISIS_L2_HELLO, ISIS_LHELLO_HLEN); + put_u8 (pkt+ 8, ifa->levels); + put_id6(pkt+ 9, p->system_id); + put_u16(pkt+15, ifa->hold_int); + // put_u16(pkt+17, ISIS_ + en->blen); + put_u8 (pkt+19, ifa->priority); + put_id7(pkt+20, 0); // xxx DR +} + +void +isis_send_ptp_hello(struct isis_iface *ifa) +{ + struct isis_proto *p = ifa->p; + + byte *pkt = isis_tx_buffer(ifa); + byte *bp = pkt + ISIS_PHELLO_HLEN; + byte *be = isis_tx_buffer_end(ifa); + + isis_fill_hdr(pkt, ISIS_PTP_HELLO, ISIS_PHELLO_HLEN); + put_u8 (pkt+ 8, ifa->levels); + put_id6(pkt+ 9, p->system_id); + put_u16(pkt+15, ifa->hold_int); + // put_u16(pkt+17, 0); /* Length postponed */ + put_u8 (pkt+19, 0); /* Fake circuit-id */ + + bp = isis_put_tlv_areas(p, bp, be); + bp = isis_put_tlv_protocols(p, bp, be); + bp = isis_put_tlv_ip4_iface_addrs(ifa, bp, be); + + put_u16(pkt+17, bp - pkt); /* Packet length */ +} + +void +isis_send_lsp(struct isis_iface *ifa, struct isis_lsp *lsp) +{ + struct isis_proto *p = ifa->p; + + byte *pkt = isis_tx_buffer(ifa); + isis_fill_hdr(pkt, xxx ? ISIS_L1_LSP : ISIS_L2_LSP, ISIS_LSP_HLEN); + put_u16(pkt+ 8, ISIS_LSP_HLEN - 1 + lsp->blen); + put_lsp_hdr(pkt+10, &lsp->hdr); + + if (lsp->body) + memcpy(pkt+26, lsp->body, lsp->blen); + + ISIS_TRACE(D_PACKETS, "Sending LSP via %s", ifa->iface->name); + // xxx sk_send_to(ifa->sk, ifa->plen, AllNodes, 0); +} + +void +isis_process_lsp(struct isis_iface *ifa, int level, byte *pkt, int len) +{ + struct isis_proto *p = ifa->p; + + if ((pkt[ISIS_HLEN_POS] != ISIS_LSP_HLEN) || (pkt[8] != len)) + XXX; + + put_u16(pkt+ 8, ISIS_LSP_HLEN - 1 + lsp->blen); + get_lsp_hdr(pkt+10, &hdr); + isis_lsp_received(db, ifa, &hdr, pkt+26, len-26) + + if (lsp->body) + memcpy(pkt+26, lsp->body, lsp->blen); + +} + + +static byte * +isis_put_tlv_lsp_entries(struct isis_iface *ifa, byte *buf, byte *be, + struct isis_lsdb *db, struct isis_lsp **lspp, int psnp) +{ + struct isis_lsp *lsp = *lspp; + byte *bp = buf + 2; + int i = 0; + + be -= 2 + 16; /* TLV header + sizeof(struct isis_lsp_hdr) */ + + while (lsp && bp <= be) + { + if (i == 15) + { + buf[0] = ISIS_TLV_LSP_ENTRIES; + buf[1] = i * 16; + i = 0; + buf = bp; + bp += 2; + } + + bp = put_lsp_hdr(bp, &lsp->hdr); + + if (psnp) + { + // XXX check + isis_lsdb_clear_ssn(db, lsp, ifa); + lsp = isis_lsdb_next_ssn(db, lsp, ifa); + } + else + lsp = isis_lsdb_next(db, lsp); + + } + buf[0] = ISIS_TLV_LSP_ENTRIES; + buf[1] = i * 16; + *lspp = lsp; +} + +void +isis_send_csnp(struct isis_iface *ifa, int level, struct isis_lsp **lspp, int first) +{ + struct isis_proto *p = ifa->p; + + byte *pkt = isis_tx_buffer(ifa); + byte *bp = pkt + ISIS_CSNP_HLEN; + byte *be = isis_tx_buffer_end(ifa); + + /* First put TLVs */ + u64 start_id = first ? ISIS_MIN_LSP_ID : (*lspp)->hdr.id; + bp = isis_put_tlv_lsp_entries(p, bp, be, p->lsdb[level], lspp, 0); + u64 end_id = *lspp ? (*lspp)->hdr.id - 1 : ISIS_MAX_LSP_ID; + + /* Then put header */ + isis_fill_hdr(pkt, !level ? ISIS_L1_CSNP : ISIS_L2_CSNP, ISIS_CSNP_HLEN); + put_u16(pkt+08, bp - pkt); + put_id7(pkt+10, p->system_id); + put_u64(pkt+17, start_id); + put_u64(pkt+25, end_id); + + ISIS_TRACE(D_PACKETS, "Sending CSNP via %s", ifa->iface->name); + // xxx sk_send_to(ifa->sk, ifa->plen, AllNodes, 0); +} + +void +isis_send_psnp(struct isis_iface *ifa, int level, struct isis_lsp **lspp) +{ + struct isis_proto *p = ifa->p; + + byte *pkt = isis_tx_buffer(ifa); + byte *bp = pkt + ISIS_PSNP_HLEN; + byte *be = isis_tx_buffer_end(ifa); + + /* First put TLVs */ + bp = isis_put_tlv_lsp_entries(p, bp, be, p->lsdb[level], lspp, 1); + + /* Then put header */ + isis_fill_hdr(pkt, !level ? ISIS_L1_PSNP : ISIS_L2_PSNP, ISIS_PSNP_HLEN); + put_u16(pkt+08, bp - pkt); + put_id7(pkt+10, p->system_id); + + ISIS_TRACE(D_PACKETS, "Sending PSNP via %s", ifa->iface->name); + // xxx sk_send_to(ifa->sk, ifa->plen, AllNodes, 0); +} + +static inline void +isis_process_tlv_lsp_entries(struct isis_iface *ifa, byte *tlv, byte *te, struct isis_lsdb *db) +{ + struct isis_proto *p = ifa->p; + struct isis_lsp_hdr hdr; + byte *bp = tlv + 2; + + while (bp + 16 <= te) + { + bp = get_lsp_hdr(bp, &hdr); + isis_snp_received(db, ifa, &hdr); + } + + if (bp < te) + XXX; +} + + +static void +isis_process_csnp(struct isis_iface *ifa, int level, byte *pkt, int len) +{ + struct isis_proto *p = ifa->p; + + if ((pkt[ISIS_HLEN_POS] != ISIS_CSNP_HLEN) || (pkt[8] != len)) + XXX; + + u64 start_id = get_u64(pkt+17); + u64 end_id = get_u64(pkt+25); + XXX; + + byte *pe = pkt + len; + byte *tlv = pkt + ISIS_CSNP_HLEN; + + while (tlv < pe) + { + byte *te = tlv + 2 + tlv[1]; + if (te > pe) + XXX; + + if (tlv[0] == ISIS_TLV_LSP_ENTRIES) + isis_process_tlv_lsp_entries(ifa, tlv, te, p->lsdb[level]); + + tlv = te; + } + +} + +static void +isis_process_psnp(struct isis_iface *ifa, int level, byte *pkt, int len) +{ + struct isis_proto *p = ifa->p; + + if ((pkt[ISIS_HLEN_POS] != ISIS_PSNP_HLEN) || (pkt[8] != len)) + XXX; + + byte *pe = pkt + len; + byte *tlv = pkt + ISIS_PSNP_HLEN; + + while (tlv < pe) + { + byte *te = tlv + 2 + tlv[1]; + if (te > pe) + XXX; + + if (tlv[0] == ISIS_TLV_LSP_ENTRIES) + isis_process_tlv_lsp_entries(ifa, tlv, te, p->lsdb[level]); + + tlv = te; + } + +} + + + +#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0) + +static int +isis_rx_hook(sock *sk, int len) +{ + struct isis_iface *ifa = sk->data; + struct isis_proto *p = ifa->p; + const char *err_dsc = NULL; + unsigned err_val = 0; + + if (sk->lifindex != sk->iface->index) + return 1; + + DBG("ISIS: RX hook called (iface %s, src %I, dst %I)\n", + sk->iface->name, sk->faddr, sk->laddr); // XXX + + // XXX check src addr + + // XXX skip eth header + byte *pkt = ip_skip_header(sk->rbuf, &len); + if (pkt == NULL) + DROP("too short", len); + + if ((len < ISIS_COMMON_HLEN) || (len < pkt[ISIS_HLEN_POS])) + DROP("too short", len); + + if (len > sk->rbsize) // XXX + DROP("too large", len); + + if (pkt[0] != ISIS_PROTO_ID) + DROP("protocol ID mismatch", pkt[0]); + + if (pkt[2] != 1) + DROP("version1 mismatch", pkt[2]); + + if (pkt[3] != 0 && pkt[3] != 6) + DROP("id_length mismatch", pkt[3]); + + if (pkt[5] != 1) + DROP("version2 mismatch", pkt[5]); + + if (pkt[7] != 0 && pkt[7] != 3) + DROP("max_area_addrs mismatch", pkt[7]); + + + // XXX find neighbor? + + int type = pkt[4] & ISIS_TYPE_MASK; + int level = ISIS_L1; + switch (type) + { + // XXX ptp + // case ISIS_PTP_HELLO: + + case ISIS_L2_HELLO: + level = ISIS_L2; + case ISIS_L1_HELLO: + ISIS_TRACE(D_PACKETS, "Received Hello from xxx via %s", ifa->iface->name); + isis_lan_hello_rx(pkt, ifa, n, level); + break; + + case ISIS_L2_LSP: + level = ISIS_L2; + case ISIS_L1_LSP: + ISIS_TRACE(D_PACKETS, "Received LSP from xxx via %s", ifa->iface->name); + isis_lsp_rx(pkt, ifa, n, level); + break; + + case ISIS_L2_CSNP: + level = ISIS_L2; + case ISIS_L1_CSNP: + ISIS_TRACE(D_PACKETS, "Received CSNP from xxx via %s", ifa->iface->name); + isis_process_csnp(pkt, ifa, n, level); + break; + + case ISIS_L2_PSNP: + level = ISIS_L2; + case ISIS_L1_PSNP: + ISIS_TRACE(D_PACKETS, "Received PSNP from xxx via %s", ifa->iface->name); + isis_psnp_received(pkt, ifa, n, level); + break; + + default: + DROP("unknown type", type); + }; + return 1; + + drop: + log(L_WARN "%s: Bad packet from %I - %s (%u)", p->p.name, sk->faddr, err_dsc, err_val); + return 1; +} + +static void +isis_tx_hook(sock *sk) +{ + struct isis_iface *ifa = sk->data; + log(L_WARN "%s: TX hook called", ifa->p->p.name); +} + +static void +isis_err_hook(sock *sk, int err) +{ + struct isis_iface *ifa = sk->data; + log(L_ERR "%s: Socket error: %m", ifa->p->p.name, err); +} + +int +isis_sk_open(struct isis_iface *ifa) +{ + sock *sk = sk_new(ifa->pool); + sk->type = SK_IP; + sk->dport = ISIS_PROTO; + sk->saddr = IPA_NONE; + + sk->ttl = 0; + sk->rx_hook = isis_rx_hook; + sk->tx_hook = isis_tx_hook; + sk->err_hook = isis_err_hook; + sk->iface = ifa->iface; + sk->rbsize = p->rx_buffer_size + 64; // XXX + sk->tbsize = p->tx_buffer_size + 64; // XXX + sk->data = ifa; + sk->flags = SKF_LADDR_RX; + + if (sk_open(sk) != 0) + goto err; + + sk->saddr = ifa->addr->ip; + + if (sk_setup_multicast(sk) < 0) + goto err; + + if (sk_join_group(sk, AllRouters) < 0) + goto err; + + ifa->sk = sk; + return 1; + + err: + rfree(sk); + return 0; +} + diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in index ac6f7a87..a08d22da 100644 --- a/sysdep/autoconf.h.in +++ b/sysdep/autoconf.h.in @@ -41,6 +41,7 @@ #undef CONFIG_RADV #undef CONFIG_BGP #undef CONFIG_OSPF +#undef CONFIG_ISIS #undef CONFIG_PIPE /* We have and syslog() */