diff options
author | Ondrej Zajicek (work) <santiago@crfreenet.org> | 2015-10-05 12:14:50 +0200 |
---|---|---|
committer | Ondrej Zajicek (work) <santiago@crfreenet.org> | 2015-10-05 13:18:10 +0200 |
commit | 8465dccb06afffed171dc1e224e4eb5f67cc3326 (patch) | |
tree | 9e5209b312ba8b7eabd0f5a22aea4a0888cd8c9f /proto | |
parent | c7b99a932cab1873042e356143ab71755920157a (diff) |
Major RIP redesign
The new RIP implementation fixes plenty of old bugs and also adds support
for many new features: ECMP support, link state support, BFD support,
configurable split horizon and more. Most options are now per-interface.
Diffstat (limited to 'proto')
-rw-r--r-- | proto/ospf/packet.c | 2 | ||||
-rw-r--r-- | proto/rip/Doc | 2 | ||||
-rw-r--r-- | proto/rip/Makefile | 2 | ||||
-rw-r--r-- | proto/rip/auth.c | 168 | ||||
-rw-r--r-- | proto/rip/config.Y | 207 | ||||
-rw-r--r-- | proto/rip/packets.c | 772 | ||||
-rw-r--r-- | proto/rip/rip.c | 1853 | ||||
-rw-r--r-- | proto/rip/rip.h | 330 |
8 files changed, 2134 insertions, 1202 deletions
diff --git a/proto/ospf/packet.c b/proto/ospf/packet.c index fb63e61c..65842037 100644 --- a/proto/ospf/packet.c +++ b/proto/ospf/packet.c @@ -223,7 +223,7 @@ ospf_rx_hook(sock *sk, int len) return 1; DBG("OSPF: RX hook called (iface %s, src %I, dst %I)\n", - sk->ifname, sk->faddr, sk->laddr); + sk->iface->name, sk->faddr, sk->laddr); /* Initially, the packet is associated with the 'master' iface */ struct ospf_iface *ifa = sk->data; diff --git a/proto/rip/Doc b/proto/rip/Doc index 2c7f4c8f..561b2153 100644 --- a/proto/rip/Doc +++ b/proto/rip/Doc @@ -1,2 +1,2 @@ S rip.c -S auth.c +S packets.c diff --git a/proto/rip/Makefile b/proto/rip/Makefile index d03e3a90..d2d3c987 100644 --- a/proto/rip/Makefile +++ b/proto/rip/Makefile @@ -1,4 +1,4 @@ -source=rip.c auth.c +source=rip.c packets.c root-rel=../../ dir-name=proto/rip diff --git a/proto/rip/auth.c b/proto/rip/auth.c deleted file mode 100644 index 5634547a..00000000 --- a/proto/rip/auth.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Rest in pieces - RIP protocol - * - * Copyright (c) 1999 Pavel Machek <pavel@ucw.cz> - * Copyright (c) 2004 Ondrej Filip <feela@network.cz> - * - * Bug fixes by Eric Leblond <eleblond@init-sys.com>, April 2003 - * - * Can be freely distributed and used under the terms of the GNU GPL. - */ - -#undef LOCAL_DEBUG - -#include "nest/bird.h" -#include "nest/iface.h" -#include "nest/protocol.h" -#include "nest/route.h" -#include "lib/socket.h" -#include "lib/resource.h" -#include "lib/lists.h" -#include "lib/timer.h" -#include "lib/md5.h" -#include "lib/string.h" - -#include "rip.h" - -#define P ((struct rip_proto *) p) -#define P_CF ((struct rip_proto_config *)p->cf) - -#define PACKETLEN(num) (num * sizeof(struct rip_block) + sizeof(struct rip_packet_heading)) - -/* - * rip_incoming_authentication - check authentication of incomming packet and return 1 if there's problem. - */ -int -rip_incoming_authentication( struct proto *p, struct rip_block_auth *block, struct rip_packet *packet, int num, ip_addr whotoldme ) -{ - DBG( "Incoming authentication: " ); - switch (ntohs(block->authtype)) { /* Authentication type */ - case AT_PLAINTEXT: - { - struct password_item *passwd = password_find(P_CF->passwords, 1); - DBG( "Plaintext passwd" ); - if (!passwd) { - log( L_AUTH "No passwords set and password authentication came" ); - return 1; - } - if (strncmp( (char *) (&block->packetlen), passwd->password, 16)) { - log( L_AUTH "Passwd authentication failed!" ); - DBG( "Expected %s, got %.16s\n", passwd->password, &block->packetlen ); - return 1; - } - } - break; - case AT_MD5: - DBG( "md5 password" ); - { - struct password_item *pass = NULL, *ptmp; - struct rip_md5_tail *tail; - struct MD5Context ctxt; - char md5sum_packet[16]; - char md5sum_computed[16]; - struct neighbor *neigh = neigh_find(p, &whotoldme, 0); - list *l = P_CF->passwords; - - if (ntohs(block->packetlen) != PACKETLEN(num) - sizeof(struct rip_md5_tail) ) { - log( L_ERR "Packet length in MD5 does not match computed value" ); - return 1; - } - - tail = (struct rip_md5_tail *) ((char *) packet + (ntohs(block->packetlen) )); - if ((tail->mustbeFFFF != 0xffff) || (tail->mustbe0001 != 0x0100)) { - log( L_ERR "MD5 tail signature is not there" ); - return 1; - } - - WALK_LIST(ptmp, *l) - { - if (block->keyid != ptmp->id) continue; - if ((ptmp->genfrom > now_real) || (ptmp->gento < now_real)) continue; - pass = ptmp; - break; - } - - if(!pass) return 1; - - if (!neigh) { - log( L_AUTH "Non-neighbour MD5 checksummed packet?" ); - } else { - if (neigh->aux > block->seq) { - log( L_AUTH "MD5 protected packet with lower numbers" ); - return 1; - } - neigh->aux = block->seq; - } - - memcpy(md5sum_packet, tail->md5, 16); - strncpy(tail->md5, pass->password, 16); - - MD5Init(&ctxt); - MD5Update(&ctxt, (char *) packet, ntohs(block->packetlen) + sizeof(struct rip_block_auth) ); - MD5Final(md5sum_computed, &ctxt); - if (memcmp(md5sum_packet, md5sum_computed, 16)) - return 1; - } - } - - return 0; -} - -/* - * rip_outgoing_authentication - append authentication information to the packet. - * %num: number of rip_blocks already in packets. This function returns size of packet to send. - */ -int -rip_outgoing_authentication( struct proto *p, struct rip_block_auth *block, struct rip_packet *packet, int num ) -{ - struct password_item *passwd = password_find(P_CF->passwords, 1); - - if (!P_CF->authtype) - return PACKETLEN(num); - - DBG( "Outgoing authentication: " ); - - if (!passwd) { - log( L_ERR "No suitable password found for authentication" ); - return PACKETLEN(num); - } - - block->authtype = htons(P_CF->authtype); - block->mustbeFFFF = 0xffff; - switch (P_CF->authtype) { - case AT_PLAINTEXT: - strncpy( (char *) (&block->packetlen), passwd->password, 16); - return PACKETLEN(num); - case AT_MD5: - { - struct rip_md5_tail *tail; - struct MD5Context ctxt; - static u32 sequence = 0; - - if (num > PACKET_MD5_MAX) - bug( "We can not add MD5 authentication to this long packet" ); - - /* need to preset the sequence number to a sane value */ - if (!sequence) - sequence = (u32) time(NULL); - - block->keyid = passwd->id; - block->authlen = sizeof(struct rip_block_auth); - block->seq = sequence++; - block->zero0 = 0; - block->zero1 = 0; - block->packetlen = htons(PACKETLEN(num)); - tail = (struct rip_md5_tail *) ((char *) packet + PACKETLEN(num) ); - tail->mustbeFFFF = 0xffff; - tail->mustbe0001 = 0x0100; - - strncpy(tail->md5, passwd->password, 16); - MD5Init(&ctxt); - MD5Update(&ctxt, (char *) packet, PACKETLEN(num) + sizeof(struct rip_md5_tail)); - MD5Final(tail->md5, &ctxt); - return PACKETLEN(num) + block->authlen; - } - default: - bug( "Unknown authtype in outgoing authentication?" ); - } -} diff --git a/proto/rip/config.Y b/proto/rip/config.Y index b2b99095..29ea7eb1 100644 --- a/proto/rip/config.Y +++ b/proto/rip/config.Y @@ -1,17 +1,14 @@ /* * BIRD -- RIP Configuration * + * (c) 1998--1999 Pavel Machek <pavel@ucw.cz> + * (c) 2004--2013 Ondrej Filip <feela@network.cz> + * (c) 2009--2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2009--2015 CZ.NIC z.s.p.o. + * * Can be freely distributed and used under the terms of the GNU GPL. */ -/* -To add: - -version1 switch - -*/ - - CF_HDR #include "proto/rip/rip.h" @@ -19,76 +16,141 @@ CF_HDR CF_DEFINES -#define RIP_CFG ((struct rip_proto_config *) this_proto) -#define RIP_IPATT ((struct rip_patt *) this_ipatt) +#define RIP_CFG ((struct rip_config *) this_proto) +#define RIP_IFACE ((struct rip_iface_config *) this_ipatt) + +static inline int rip_cfg_is_v2(void) { return RIP_CFG->rip2; } +static inline int rip_cfg_is_ng(void) { return ! RIP_CFG->rip2; } + +static inline void +rip_check_auth(void) +{ + if (rip_cfg_is_ng()) + cf_error("Authentication not supported in RIPng"); +} -#ifdef IPV6 -#define RIP_DEFAULT_TTL_SECURITY 2 -#else -#define RIP_DEFAULT_TTL_SECURITY 0 -#endif CF_DECLS -CF_KEYWORDS(RIP, INFINITY, METRIC, PORT, PERIOD, GARBAGE, TIMEOUT, - MODE, BROADCAST, MULTICAST, QUIET, NOLISTEN, VERSION1, - AUTHENTICATION, NONE, PLAINTEXT, MD5, TTL, SECURITY, - HONOR, NEVER, NEIGHBOR, ALWAYS, TX, PRIORITY, ONLY, - RIP_METRIC, RIP_TAG) +CF_KEYWORDS(RIP, ECMP, LIMIT, WEIGHT, INFINITY, METRIC, UPDATE, TIMEOUT, + GARBAGE, PORT, ADDRESS, MODE, BROADCAST, MULTICAST, PASSIVE, + VERSION, SPLIT, HORIZON, POISON, REVERSE, CHECK, ZERO, TIME, BFD, + AUTHENTICATION, NONE, PLAINTEXT, CRYPTOGRAPHIC, MD5, TTL, SECURITY, + RX, TX, BUFFER, LENGTH, PRIORITY, ONLY, LINK, RIP_METRIC, RIP_TAG) -%type <i> rip_mode rip_auth +%type <i> rip_auth CF_GRAMMAR -CF_ADDTO(proto, rip_cfg '}' { RIP_CFG->passwords = get_passwords(); } ) +CF_ADDTO(proto, rip_proto) -rip_cfg_start: proto_start RIP { - this_proto = proto_config_new(&proto_rip, $1); - rip_init_config(RIP_CFG); - } - ; +rip_proto_start: proto_start RIP +{ + this_proto = proto_config_new(&proto_rip, $1); + init_list(&RIP_CFG->patt_list); -rip_cfg: - rip_cfg_start proto_name '{' - | rip_cfg proto_item ';' - | rip_cfg INFINITY expr ';' { RIP_CFG->infinity = $3; } - | rip_cfg PORT expr ';' { RIP_CFG->port = $3; } - | rip_cfg PERIOD expr ';' { RIP_CFG->period = $3; } - | rip_cfg GARBAGE TIME expr ';' { RIP_CFG->garbage_time = $4; } - | rip_cfg TIMEOUT TIME expr ';' { RIP_CFG->timeout_time = $4; } - | rip_cfg AUTHENTICATION rip_auth ';' {RIP_CFG->authtype = $3; } - | rip_cfg password_list ';' - | rip_cfg HONOR ALWAYS ';' { RIP_CFG->honor = HO_ALWAYS; } - | rip_cfg HONOR NEIGHBOR ';' { RIP_CFG->honor = HO_NEIGHBOR; } - | rip_cfg HONOR NEVER ';' { RIP_CFG->honor = HO_NEVER; } - | rip_cfg INTERFACE rip_iface ';' - ; + RIP_CFG->rip2 = RIP_IS_V2; + RIP_CFG->infinity = RIP_DEFAULT_INFINITY; -rip_auth: - PLAINTEXT { $$=AT_PLAINTEXT; } - | MD5 { $$=AT_MD5; } - | NONE { $$=AT_NONE; } - ; + RIP_CFG->min_timeout_time = 60; + RIP_CFG->max_garbage_time = 60; +}; +rip_proto_item: + proto_item + | ECMP bool { RIP_CFG->ecmp = $2 ? RIP_DEFAULT_ECMP_LIMIT : 0; } + | ECMP bool LIMIT expr { RIP_CFG->ecmp = $2 ? $4 : 0; if ($4 < 0) cf_error("ECMP limit cannot be negative"); } + | INFINITY expr { RIP_CFG->infinity = $2; } + | INTERFACE rip_iface + ; -rip_mode: - BROADCAST { $$=IM_BROADCAST; } - | MULTICAST { $$=0; } - | QUIET { $$=IM_QUIET; } - | NOLISTEN { $$=IM_NOLISTEN; } - | VERSION1 { $$=IM_VERSION1 | IM_BROADCAST; } +rip_proto_opts: + /* empty */ + | rip_proto_opts rip_proto_item ';' ; +rip_proto: + rip_proto_start proto_name '{' rip_proto_opts '}'; + + +rip_iface_start: +{ + this_ipatt = cfg_allocz(sizeof(struct rip_iface_config)); + add_tail(&RIP_CFG->patt_list, NODE this_ipatt); + init_list(&this_ipatt->ipn_list); + reset_passwords(); + + RIP_IFACE->metric = 1; + RIP_IFACE->port = rip_cfg_is_v2() ? RIP_PORT : RIP_NG_PORT; + RIP_IFACE->version = rip_cfg_is_v2() ? RIP_V2 : RIP_V1; + RIP_IFACE->split_horizon = 1; + RIP_IFACE->poison_reverse = 1; + RIP_IFACE->check_zero = 1; + RIP_IFACE->ttl_security = rip_cfg_is_v2() ? 0 : 1; + RIP_IFACE->rx_buffer = rip_cfg_is_v2() ? RIP_MAX_PKT_LENGTH : 0; + RIP_IFACE->tx_length = rip_cfg_is_v2() ? RIP_MAX_PKT_LENGTH : 0; + RIP_IFACE->tx_tos = IP_PREC_INTERNET_CONTROL; + RIP_IFACE->tx_priority = sk_priority_control; + RIP_IFACE->update_time = RIP_DEFAULT_UPDATE_TIME; + RIP_IFACE->timeout_time = RIP_DEFAULT_TIMEOUT_TIME; + RIP_IFACE->garbage_time = RIP_DEFAULT_GARBAGE_TIME; +}; + +rip_iface_finish: +{ + RIP_IFACE->passwords = get_passwords(); + + if (!RIP_IFACE->auth_type != !RIP_IFACE->passwords) + log(L_WARN "Authentication and password options should be used together"); + + /* Default mode is broadcast for RIPv1, multicast for RIPv2 and RIPng */ + if (!RIP_IFACE->mode) + RIP_IFACE->mode = (rip_cfg_is_v2() && (RIP_IFACE->version == RIP_V1)) ? + RIP_IM_BROADCAST : RIP_IM_MULTICAST; + + RIP_CFG->min_timeout_time = MIN_(RIP_CFG->min_timeout_time, RIP_IFACE->timeout_time); + RIP_CFG->max_garbage_time = MAX_(RIP_CFG->max_garbage_time, RIP_IFACE->garbage_time); +}; + rip_iface_item: - | METRIC expr { RIP_IPATT->metric = $2; } - | MODE rip_mode { RIP_IPATT->mode |= $2; } - | TX tos { RIP_IPATT->tx_tos = $2; } - | TX PRIORITY expr { RIP_IPATT->tx_priority = $3; } - | TTL SECURITY bool { RIP_IPATT->ttl_security = $3; } - | TTL SECURITY TX ONLY { RIP_IPATT->ttl_security = 2; } + METRIC expr { RIP_IFACE->metric = $2; if (($2<1) || ($2>255)) cf_error("Metric must be in range 1-255"); } + | MODE MULTICAST { RIP_IFACE->mode = RIP_IM_MULTICAST; } + | MODE BROADCAST { RIP_IFACE->mode = RIP_IM_BROADCAST; if (rip_cfg_is_ng()) cf_error("Broadcast not supported in RIPng"); } + | PASSIVE bool { RIP_IFACE->passive = $2; } + | ADDRESS ipa { RIP_IFACE->address = $2; } + | PORT expr { RIP_IFACE->port = $2; if (($2<1) || ($2>65535)) cf_error("Invalid port number"); } + | VERSION expr { RIP_IFACE->version = $2; + if (rip_cfg_is_ng()) cf_error("Version not supported in RIPng"); + if (($2 != RIP_V1) && ($2 != RIP_V2)) cf_error("Unsupported version"); + } + | VERSION ONLY bool { RIP_IFACE->version_only = $3; } + | SPLIT HORIZON bool { RIP_IFACE->split_horizon = $3; } + | POISON REVERSE bool { RIP_IFACE->poison_reverse = $3; } + | CHECK ZERO bool { RIP_IFACE->check_zero = $3; } + | UPDATE TIME expr { RIP_IFACE->update_time = $3; if ($3<=0) cf_error("Update time must be positive"); } + | TIMEOUT TIME expr { RIP_IFACE->timeout_time = $3; if ($3<=0) cf_error("Timeout time must be positive"); } + | GARBAGE TIME expr { RIP_IFACE->garbage_time = $3; if ($3<=0) cf_error("Garbage time must be positive"); } + | ECMP WEIGHT expr { RIP_IFACE->ecmp_weight = $3 - 1; if (($3<1) || ($3>256)) cf_error("ECMP weight must be in range 1-256"); } + | RX BUFFER expr { RIP_IFACE->rx_buffer = $3; if (($3<256) || ($3>65535)) cf_error("TX length must be in range 256-65535"); } + | TX LENGTH expr { RIP_IFACE->tx_length = $3; if (($3<256) || ($3>65535)) cf_error("TX length must be in range 256-65535"); } + | TX tos { RIP_IFACE->tx_tos = $2; } + | TX PRIORITY expr { RIP_IFACE->tx_priority = $3; } + | TTL SECURITY bool { RIP_IFACE->ttl_security = $3; } + | TTL SECURITY TX ONLY { RIP_IFACE->ttl_security = 2; } + | CHECK LINK bool { RIP_IFACE->check_link = $3; } + | BFD bool { RIP_IFACE->bfd = $2; cf_check_bfd($2); } + | AUTHENTICATION rip_auth { RIP_IFACE->auth_type = $2; if ($2) rip_check_auth(); } + | password_list { rip_check_auth(); } +; + +rip_auth: + NONE { $$ = RIP_AUTH_NONE; } + | PLAINTEXT { $$ = RIP_AUTH_PLAIN; } + | CRYPTOGRAPHIC { $$ = RIP_AUTH_CRYPTO; } + | MD5 { $$ = RIP_AUTH_CRYPTO; } ; -rip_iface_opts: +rip_iface_opts: /* empty */ | rip_iface_opts rip_iface_item ';' ; @@ -98,25 +160,22 @@ rip_iface_opt_list: | '{' rip_iface_opts '}' ; -rip_iface_init: - /* EMPTY */ { - this_ipatt = cfg_allocz(sizeof(struct rip_patt)); - add_tail(&RIP_CFG->iface_list, NODE this_ipatt); - init_list(&this_ipatt->ipn_list); - RIP_IPATT->metric = 1; - RIP_IPATT->tx_tos = IP_PREC_INTERNET_CONTROL; - RIP_IPATT->tx_priority = sk_priority_control; - RIP_IPATT->ttl_security = RIP_DEFAULT_TTL_SECURITY; - } - ; +rip_iface: + rip_iface_start iface_patt_list_nopx rip_iface_opt_list rip_iface_finish; -rip_iface: /* TODO: switch to iface_patt_list_nopx */ - rip_iface_init iface_patt_list rip_iface_opt_list - ; CF_ADDTO(dynamic_attr, RIP_METRIC { $$ = f_new_dynamic_attr(EAF_TYPE_INT | EAF_TEMP, T_INT, EA_RIP_METRIC); }) CF_ADDTO(dynamic_attr, RIP_TAG { $$ = f_new_dynamic_attr(EAF_TYPE_INT | EAF_TEMP, T_INT, EA_RIP_TAG); }) +CF_CLI_HELP(SHOW RIP, ..., [[Show information about RIP protocol]]); + +CF_CLI(SHOW RIP INTERFACES, optsym opttext, [<name>] [\"<interface>\"], [[Show information about RIP interfaces]]) +{ rip_show_interfaces(proto_get_named($4, &proto_rip), $5); }; + +CF_CLI(SHOW RIP NEIGHBORS, optsym opttext, [<name>] [\"<interface>\"], [[Show information about RIP neighbors]]) +{ rip_show_neighbors(proto_get_named($4, &proto_rip), $5); }; + + CF_CODE CF_END diff --git a/proto/rip/packets.c b/proto/rip/packets.c new file mode 100644 index 00000000..be20734f --- /dev/null +++ b/proto/rip/packets.c @@ -0,0 +1,772 @@ +/* + * BIRD -- Routing Information Protocol (RIP) + * + * (c) 1998--1999 Pavel Machek <pavel@ucw.cz> + * (c) 2004--2013 Ondrej Filip <feela@network.cz> + * (c) 2009--2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2009--2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "rip.h" +#include "lib/md5.h" + + +#define RIP_CMD_REQUEST 1 /* want info */ +#define RIP_CMD_RESPONSE 2 /* responding to request */ + +#define RIP_BLOCK_LENGTH 20 + +#define RIP_PASSWD_LENGTH 16 +#define RIP_MD5_LENGTH 16 + +#define RIP_AF_IPV4 2 +#define RIP_AF_AUTH 0xffff + + +/* RIP packet header */ +struct rip_packet +{ + u8 command; + u8 version; + u16 unused; +}; + +/* RTE block for RIPv2 */ +struct rip_block_v2 +{ + u16 family; + u16 tag; + ip4_addr network; + ip4_addr netmask; + ip4_addr next_hop; + u32 metric; +}; + +/* RTE block for RIPng */ +struct rip_block_ng +{ + ip6_addr prefix; + u16 tag; + u8 pxlen; + u8 metric; +}; + +/* Authentication block for RIPv2 */ +struct rip_block_auth +{ + u16 must_be_ffff; + u16 auth_type; + char password[0]; + u16 packet_len; + u8 key_id; + u8 auth_len; + u32 seq_num; + u32 unused1; + u32 unused2; +}; + +/* Authentication tail, RFC 4822 */ +struct rip_auth_tail +{ + u16 must_be_ffff; + u16 must_be_0001; + byte auth_data[]; +}; + +/* Internal representation of RTE block data */ +struct rip_block +{ + ip_addr prefix; + int pxlen; + u32 metric; + u16 tag; + u16 no_af; + ip_addr next_hop; +}; + + +#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0) +#define DROP1(DSC) do { err_dsc = DSC; goto drop; } while(0) +#define SKIP(DSC) do { err_dsc = DSC; goto skip; } while(0) + +#define LOG_PKT(msg, args...) \ + log_rl(&p->log_pkt_tbf, L_REMOTE "%s: " msg, p->p.name, args) + +#define LOG_PKT_AUTH(msg, args...) \ + log_rl(&p->log_pkt_tbf, L_AUTH "%s: " msg, p->p.name, args) + +#define LOG_RTE(msg, args...) \ + log_rl(&p->log_rte_tbf, L_REMOTE "%s: " msg, p->p.name, args) + + +static inline void * rip_tx_buffer(struct rip_iface *ifa) +{ return ifa->sk->tbuf; } + +static inline uint rip_pkt_hdrlen(struct rip_iface *ifa) +{ return sizeof(struct rip_packet) + (ifa->cf->auth_type ? RIP_BLOCK_LENGTH : 0); } + +static inline void +rip_put_block(struct rip_proto *p, byte *pos, struct rip_block *rte) +{ + if (rip_is_v2(p)) + { + struct rip_block_v2 *block = (void *) pos; + block->family = rte->no_af ? 0 : htons(RIP_AF_IPV4); + block->tag = htons(rte->tag); + block->network = ip4_hton(ipa_to_ip4(rte->prefix)); + block->netmask = ip4_hton(ip4_mkmask(rte->pxlen)); + block->next_hop = ip4_hton(ipa_to_ip4(rte->next_hop)); + block->metric = htonl(rte->metric); + } + else /* RIPng */ + { + struct rip_block_ng *block = (void *) pos; + block->prefix = ip6_hton(ipa_to_ip6(rte->prefix)); + block->tag = htons(rte->tag); + block->pxlen = rte->pxlen; + block->metric = rte->metric; + } +} + +static inline void +rip_put_next_hop(struct rip_proto *p, byte *pos, struct rip_block *rte) +{ + struct rip_block_ng *block = (void *) pos; + block->prefix = ip6_hton(ipa_to_ip6(rte->next_hop)); + block->tag = 0; + block->pxlen = 0; + block->metric = 0xff; +} + +static inline int +rip_get_block(struct rip_proto *p, byte *pos, struct rip_block *rte) +{ + if (rip_is_v2(p)) + { + struct rip_block_v2 *block = (void *) pos; + + /* Skip blocks with strange AF, including authentication blocks */ + if (block->family != (rte->no_af ? 0 : htons(RIP_AF_IPV4))) + return 0; + + rte->prefix = ipa_from_ip4(ip4_ntoh(block->network)); + rte->pxlen = ip4_masklen(ip4_ntoh(block->netmask)); + rte->metric = ntohl(block->metric); + rte->tag = ntohs(block->tag); + rte->next_hop = ipa_from_ip4(ip4_ntoh(block->next_hop)); + + return 1; + } + else /* RIPng */ + { + struct rip_block_ng *block = (void *) pos; + + /* Handle and skip next hop blocks */ + if (block->metric == 0xff) + { + rte->next_hop = ipa_from_ip6(ip6_ntoh(block->prefix)); + if (!ipa_is_link_local(rte->next_hop)) rte->next_hop = IPA_NONE; + return 0; + } + + rte->prefix = ipa_from_ip6(ip6_ntoh(block->prefix)); + rte->pxlen = block->pxlen; + rte->metric = block->metric; + rte->tag = ntohs(block->tag); + /* rte->next_hop is deliberately kept unmodified */; + + return 1; + } +} + +static inline void +rip_update_csn(struct rip_proto *p UNUSED, struct rip_iface *ifa) +{ + /* + * We update crypto sequence numbers at the beginning of update session to + * avoid issues with packet reordering, so packets inside one update session + * have the same CSN. We are using real time, but enforcing monotonicity. + */ + if (ifa->cf->auth_type == RIP_AUTH_CRYPTO) + ifa->csn = (ifa->csn < (u32) now_real) ? (u32) now_real : ifa->csn + 1; +} + +static void +rip_fill_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint *plen) +{ + struct rip_block_auth *auth = (void *) (pkt + 1); + struct password_item *pass = password_find(ifa->cf->passwords, 0); + + if (!pass) + { + /* FIXME: This should not happen */ + log(L_ERR "%s: No suitable password found for authentication", p->p.name); + memset(auth, 0, sizeof(struct rip_block_auth)); + return; + } + + switch (ifa->cf->auth_type) + { + case RIP_AUTH_PLAIN: + auth->must_be_ffff = htons(0xffff); + auth->auth_type = htons(RIP_AUTH_PLAIN); + strncpy(auth->password, pass->password, RIP_PASSWD_LENGTH); + return; + + case RIP_AUTH_CRYPTO: + auth->must_be_ffff = htons(0xffff); + auth->auth_type = htons(RIP_AUTH_CRYPTO); + auth->packet_len = htons(*plen); + auth->key_id = pass->id; + auth->auth_len = sizeof(struct rip_auth_tail) + RIP_MD5_LENGTH; + auth->seq_num = ifa->csn_ready ? htonl(ifa->csn) : 0; + auth->unused1 = 0; + auth->unused2 = 0; + ifa->csn_ready = 1; + + /* + * Note that RFC 4822 is unclear whether auth_len should cover whole + * authentication trailer or just auth_data length. + * + * Crypto sequence numbers are increased by sender in rip_update_csn(). + * First CSN should be zero, this is handled by csn_ready. + */ + + struct rip_auth_tail *tail = (void *) ((byte *) pkt + *plen); + tail->must_be_ffff = htons(0xffff); + tail->must_be_0001 = htons(0x0001); + strncpy(tail->auth_data, pass->password, RIP_MD5_LENGTH); + + *plen += sizeof(struct rip_auth_tail) + RIP_MD5_LENGTH; + + struct MD5Context ctxt; + MD5Init(&ctxt); + MD5Update(&ctxt, (byte *) pkt, *plen); + MD5Final(tail->auth_data, &ctxt); + return; + + default: + bug("Unknown authentication type"); + } +} + +static int +rip_check_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint *plen, struct rip_neighbor *n) +{ + struct rip_block_auth *auth = (void *) (pkt + 1); + struct password_item *pass = NULL; + const char *err_dsc = NULL; + uint err_val = 0; + uint auth_type = 0; + + /* Check for authentication entry */ + if ((*plen >= (sizeof(struct rip_packet) + sizeof(struct rip_block_auth))) && + (auth->must_be_ffff == htons(0xffff))) + auth_type = ntohs(auth->auth_type); + + if (auth_type != ifa->cf->auth_type) + DROP("authentication method mismatch", auth_type); + + switch (auth_type) + { + case RIP_AUTH_NONE: + return 1; + + case RIP_AUTH_PLAIN: + pass = password_find_by_value(ifa->cf->passwords, auth->password, RIP_PASSWD_LENGTH); + if (!pass) + DROP1("wrong password"); + + return 1; + + case RIP_AUTH_CRYPTO: + pass = password_find_by_id(ifa->cf->passwords, auth->key_id); + if (!pass) + DROP("no suitable password found", auth->key_id); + + uint data_len = ntohs(auth->packet_len); + uint auth_len = sizeof(struct rip_auth_tail) + RIP_MD5_LENGTH; + + if (data_len + auth_len != *plen) + DROP("packet length mismatch", data_len); + + if ((auth->auth_len != RIP_MD5_LENGTH) && (auth->auth_len != auth_len)) + DROP("authentication data length mismatch", auth->auth_len); + + struct rip_auth_tail *tail = (void *) ((byte *) pkt + data_len); + if ((tail->must_be_ffff != htons(0xffff)) || (tail->must_be_0001 != htons(0x0001))) + DROP1("authentication trailer is missing"); + + /* Accept higher sequence number, or zero if connectivity is lost */ + /* FIXME: sequence number must be password/SA specific */ + u32 rcv_csn = ntohl(auth->seq_num); + if ((rcv_csn < n->csn) && (rcv_csn || n->uc)) + { + /* We want to report both new and old CSN */ + LOG_PKT_AUTH("Authentication failed for %I on %s - " + "lower sequence number (rcv %u, old %u)", + n->nbr->addr, ifa->iface->name, rcv_csn, n->csn); + return 0; + } + + char received[RIP_MD5_LENGTH]; + char computed[RIP_MD5_LENGTH]; + + memcpy(received, tail->auth_data, RIP_MD5_LENGTH); + strncpy(tail->auth_data, pass->password, RIP_MD5_LENGTH); + + struct MD5Context ctxt; + MD5Init(&ctxt); + MD5Update(&ctxt, (byte *) pkt, *plen); + MD5Final(computed, &ctxt); + + if (memcmp(received, computed, RIP_MD5_LENGTH)) + DROP("wrong MD5 digest", pass->id); + + *plen = data_len; + n->csn = rcv_csn; + + return 1; + } + +drop: + LOG_PKT_AUTH("Authentication failed for %I on %s - %s (%u)", + n->nbr->addr, ifa->iface->name, err_dsc, err_val); + + return 0; +} + +static inline int +rip_send_to(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint plen, ip_addr dst) +{ + if (ifa->cf->auth_type) + rip_fill_authentication(p, ifa, pkt, &plen); + + return sk_send_to(ifa->sk, plen, dst, 0); +} + + +void +rip_send_request(struct rip_proto *p, struct rip_iface *ifa) +{ + byte *pos = rip_tx_buffer(ifa); + + struct rip_packet *pkt = (void *) pos; + pkt->command = RIP_CMD_REQUEST; + pkt->version = ifa->cf->version; + pkt->unused = 0; + pos += rip_pkt_hdrlen(ifa); + + struct rip_block b = { .no_af = 1, .metric = p->infinity }; + rip_put_block(p, pos, &b); + pos += RIP_BLOCK_LENGTH; + + rip_update_csn(p, ifa); + + TRACE(D_PACKETS, "Sending request via %s", ifa->iface->name); + rip_send_to(p, ifa, pkt, pos - (byte *) pkt, ifa->addr); +} + +static void +rip_receive_request(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint plen, struct rip_neighbor *from) +{ + TRACE(D_PACKETS, "Request received from %I on %s", from->nbr->addr, ifa->iface->name); + + byte *pos = (byte *) pkt + rip_pkt_hdrlen(ifa); + + /* We expect one regular block */ + if (plen != (rip_pkt_hdrlen(ifa) + RIP_BLOCK_LENGTH)) + return; + + struct rip_block b = { .no_af = 1 }; + + if (!rip_get_block(p, pos, &b)) + return; + + /* Special case - zero prefix, infinity metric */ + if (ipa_nonzero(b.prefix) || b.pxlen || (b.metric != p->infinity)) + return; + + /* We do nothing if TX is already active */ + if (ifa->tx_active) + { + TRACE(D_EVENTS, "Skipping request from %I on %s, TX is busy", from->nbr->addr, ifa->iface->name); + return; + } + + if (!ifa->cf->passive) + rip_send_table(p, ifa, from->nbr->addr, 0); +} + + +static int +rip_send_response(struct rip_proto *p, struct rip_iface *ifa) +{ + if (! ifa->tx_active) + return 0; + + byte *pos = rip_tx_buffer(ifa); + byte *max = rip_tx_buffer(ifa) + ifa->tx_plen - + (rip_is_v2(p) ? RIP_BLOCK_LENGTH : 2*RIP_BLOCK_LENGTH); + ip_addr last_next_hop = IPA_NONE; + int send = 0; + + struct rip_packet *pkt = (void *) pos; + pkt->command = RIP_CMD_RESPONSE; + pkt->version = ifa->cf->version; + pkt->unused = 0; + pos += rip_pkt_hdrlen(ifa); + + FIB_ITERATE_START(&p->rtable, &ifa->tx_fit, z) + { + struct rip_entry *en = (struct rip_entry *) z; + + /* Dummy entries */ + if (!en->valid) + goto next_entry; + + /* Stale entries that should be removed */ + if ((en->valid == RIP_ENTRY_STALE) && + ((en->changed + ifa->cf->garbage_time) <= now)) + goto next_entry; + + /* Triggered updates */ + if (en->changed < ifa->tx_changed) + goto next_entry; + + /* Not enough space for current entry */ + if (pos > max) + { + FIB_ITERATE_PUT(&ifa->tx_fit, z); + goto break_loop; + } + + struct rip_block rte = { + .prefix = en->n.prefix, + .pxlen = en->n.pxlen, + .metric = en->metric, + .tag = en->tag + }; + + if (en->iface == ifa->iface) + rte.next_hop = en->next_hop; + + if (rip_is_v2(p) && (ifa->cf->version == RIP_V1)) + { + /* Skipping subnets (i.e. not hosts, classful networks or default route) */ + if (ip4_masklen(ip4_class_mask(ipa_to_ip4(en->n.prefix))) != en->n.pxlen) + goto next_entry; + + rte.tag = 0; + rte.pxlen = 0; + rte.next_hop = IPA_NONE; + } + + /* Split horizon */ + if (en->from == ifa->iface && ifa->cf->split_horizon) + { + if (ifa->cf->poison_reverse) + { + rte.metric = p->infinity; + rte.next_hop = IPA_NONE; + } + else + goto next_entry; + } + + // TRACE(D_PACKETS, " %I/%d -> %I metric %d", rte.prefix, rte.pxlen, rte.next_hop, rte.metric); + + /* RIPng next hop entry */ + if (rip_is_ng(p) && !ipa_equal(rte.next_hop, last_next_hop)) + { + last_next_hop = rte.next_hop; + rip_put_next_hop(p, pos, &rte); + pos += RIP_BLOCK_LENGTH; + } + + rip_put_block(p, pos, &rte); + pos += RIP_BLOCK_LENGTH; + send = 1; + + next_entry: ; + } + FIB_ITERATE_END(z); + ifa->tx_active = 0; + + /* Do not send empty packet */ + if (!send) + return 0; + +break_loop: + TRACE(D_PACKETS, "Sending response via %s", ifa->iface->name); + return rip_send_to(p, ifa, pkt, pos - (byte *) pkt, ifa->tx_addr); +} + +/** + * rip_send_table - RIP interface timer hook + * @p: RIP instance + * @ifa: RIP interface + * @addr: destination IP address + * @changed: time limit for triggered updates + * + * The function activates an update session and starts sending routing update + * packets (using rip_send_response()). The session may be finished during the + * call or may continue in rip_tx_hook() until all appropriate routes are + * transmitted. Note that there may be at most one active update session per + * interface, the function will terminate the old active session before + * activating the new one. + */ +void +rip_send_table(struct rip_proto *p, struct rip_iface *ifa, ip_addr addr, bird_clock_t changed) +{ + DBG("RIP: Opening TX session to %I on %s\n", dst, ifa->iface->name); + + rip_reset_tx_session(p, ifa); + + ifa->tx_active = 1; + ifa->tx_addr = addr; + ifa->tx_changed = changed; + FIB_ITERATE_INIT(&ifa->tx_fit, &p->rtable); + + rip_update_csn(p, ifa); + + while (rip_send_response(p, ifa) > 0) + ; +} + +static void +rip_tx_hook(sock *sk) +{ + struct rip_iface *ifa = sk->data; + struct rip_proto *p = ifa->rip; + + DBG("RIP: TX hook called (iface %s, src %I, dst %I)\n", + sk->iface->name, sk->saddr, sk->daddr); + + while (rip_send_response(p, ifa) > 0) + ; +} + +static void +rip_err_hook(sock *sk, int err) +{ + struct rip_iface *ifa = sk->data; + struct rip_proto *p = ifa->rip; + + log(L_ERR "%s: Socket error on %s: %M", p->p.name, ifa->iface->name, err); + + rip_reset_tx_session(p, ifa); +} + +static void +rip_receive_response(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint plen, struct rip_neighbor *from) +{ + struct rip_block rte = {}; + const char *err_dsc = NULL; + + TRACE(D_PACKETS, "Response received from %I on %s", from->nbr->addr, ifa->iface->name); + + byte *pos = (byte *) pkt + sizeof(struct rip_packet); + byte *end = (byte *) pkt + plen; + + for (; pos < end; pos += RIP_BLOCK_LENGTH) + { + /* Find next regular RTE */ + if (!rip_get_block(p, pos, &rte)) + continue; + + int c = ipa_classify_net(rte.prefix); + if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK)) + SKIP("invalid prefix"); + + if (rip_is_v2(p) && (pkt->version == RIP_V1)) + { + if (ifa->cf->check_zero && (rte.tag || rte.pxlen || ipa_nonzero(rte.next_hop))) + SKIP("RIPv1 reserved field is nonzero"); + + rte.tag = 0; + rte.pxlen = ip4_masklen(ip4_class_mask(ipa_to_ip4(rte.prefix))); + rte.next_hop = IPA_NONE; + } + + if ((rte.pxlen < 0) || (rte.pxlen > MAX_PREFIX_LENGTH)) + SKIP("invalid prefix length"); + + if (rte.metric > p->infinity) + SKIP("invalid metric"); + + if (ipa_nonzero(rte.next_hop)) + { + neighbor *nbr = neigh_find2(&p->p, &rte.next_hop, ifa->iface, 0); + if (!nbr || (nbr->scope <= 0)) + rte.next_hop = IPA_NONE; + } + + // TRACE(D_PACKETS, " %I/%d -> %I metric %d", rte.prefix, rte.pxlen, rte.next_hop, rte.metric); + + rte.metric += ifa->cf->metric; + + if (rte.metric < p->infinity) + { + struct rip_rte new = { + .from = from, + .next_hop = ipa_nonzero(rte.next_hop) ? rte.next_hop : from->nbr->addr, + .metric = rte.metric, + .tag = rte.tag, + .expires = now + ifa->cf->timeout_time + }; + + rip_update_rte(p, &rte.prefix, rte.pxlen, &new); + } + else + rip_withdraw_rte(p, &rte.prefix, rte.pxlen, from); + + continue; + + skip: + LOG_RTE("Ignoring route %I/%d received from %I - %s", + rte.prefix, rte.pxlen, from->nbr->addr, err_dsc); + } +} + +static int +rip_rx_hook(sock *sk, int len) +{ + struct rip_iface *ifa = sk->data; + struct rip_proto *p = ifa->rip; + const char *err_dsc = NULL; + uint err_val = 0; + + if (sk->lifindex != sk->iface->index) + return 1; + + DBG("RIP: RX hook called (iface %s, src %I, dst %I)\n", + sk->iface->name, sk->faddr, sk->laddr); + + /* Silently ignore my own packets */ + /* FIXME: Better local address check */ + if (ipa_equal(ifa->iface->addr->ip, sk->faddr)) + return 1; + + if (rip_is_ng(p) && !ipa_is_link_local(sk->faddr)) + DROP1("wrong src address"); + + struct rip_neighbor *n = rip_get_neighbor(p, &sk->faddr, ifa); + + if (!n) + DROP1("not from neighbor"); + + if ((ifa->cf->ttl_security == 1) && (sk->rcv_ttl < 255)) + DROP("wrong TTL", sk->rcv_ttl); + + if (sk->fport != sk->dport) + DROP("wrong src port", sk->fport); + + if (len < sizeof(struct rip_packet)) + DROP("too short", len); + + if (sk->flags & SKF_TRUNCATED) + DROP("truncated", len); + + struct rip_packet *pkt = (struct rip_packet *) sk->rbuf; + uint plen = len; + + if (!pkt->version || (ifa->cf->version_only && (pkt->version != ifa->cf->version))) + DROP("wrong version", pkt->version); + + /* rip_check_authentication() has its own error logging */ + if (rip_is_v2(p) && !rip_check_authentication(p, ifa, pkt, &plen, n)) + return 1; + + if ((plen - sizeof(struct rip_packet)) % RIP_BLOCK_LENGTH) + DROP("invalid length", plen); + + n->last_seen = now; + rip_update_bfd(p, n); + + switch (pkt->command) + { + case RIP_CMD_REQUEST: + rip_receive_request(p, ifa, pkt, plen, n); + break; + + case RIP_CMD_RESPONSE: + rip_receive_response(p, ifa, pkt, plen, n); + break; + + default: + DROP("unknown command", pkt->command); + } + return 1; + +drop: + LOG_PKT("Bad packet from %I via %s - %s (%u)", + sk->faddr, sk->iface->name, err_dsc, err_val); + + return 1; +} + +int +rip_open_socket(struct rip_iface *ifa) +{ + struct rip_proto *p = ifa->rip; + + sock *sk = sk_new(p->p.pool); + sk->type = SK_UDP; + sk->sport = ifa->cf->port; + sk->dport = ifa->cf->port; + sk->iface = ifa->iface; + + /* + * For RIPv2, we explicitly choose a primary address, mainly to ensure that + * RIP and BFD uses the same one. For RIPng, we left it to kernel, which + * should choose some link-local address based on the same scope rule. + */ + if (rip_is_v2(p)) + sk->saddr = ifa->iface->addr->ip; + + sk->rx_hook = rip_rx_hook; + sk->tx_hook = rip_tx_hook; + sk->err_hook = rip_err_hook; + sk->data = ifa; + + sk->tos = ifa->cf->tx_tos; + sk->priority = ifa->cf->tx_priority; + sk->ttl = ifa->cf->ttl_security ? 255 : 1; + sk->flags = SKF_LADDR_RX | ((ifa->cf->ttl_security == 1) ? SKF_TTL_RX : 0); + + /* sk->rbsize and sk->tbsize are handled in rip_iface_update_buffers() */ + + if (sk_open(sk) < 0) + goto err; + + if (ifa->cf->mode == RIP_IM_MULTICAST) + { + if (sk_setup_multicast(sk) < 0) + goto err; + + if (sk_join_group(sk, ifa->addr) < 0) + goto err; + } + else /* Broadcast */ + { + if (sk_setup_broadcast(sk) < 0) + goto err; + + if (ipa_zero(ifa->addr)) + { + sk->err = "Missing broadcast address"; + goto err; + } + } + + ifa->sk = sk; + return 1; + +err: + sk_log_error(sk, p->p.name); + rfree(sk); + return 0; +} diff --git a/proto/rip/rip.c b/proto/rip/rip.c index b77cf409..c85fd69b 100644 --- a/proto/rip/rip.c +++ b/proto/rip/rip.c @@ -1,1047 +1,1275 @@ /* - * Rest in pieces - RIP protocol + * BIRD -- Routing Information Protocol (RIP) * - * Copyright (c) 1998, 1999 Pavel Machek <pavel@ucw.cz> - * 2004 Ondrej Filip <feela@network.cz> + * (c) 1998--1999 Pavel Machek <pavel@ucw.cz> + * (c) 2004--2013 Ondrej Filip <feela@network.cz> + * (c) 2009--2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2009--2015 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. - * - FIXME: IPv6 support: packet size - FIXME: (nonurgent) IPv6 support: receive "route using" blocks - FIXME: (nonurgent) IPv6 support: generate "nexthop" blocks - next hops are only advisory, and they are pretty ugly in IPv6. - I suggest just forgetting about them. - - FIXME: (nonurgent): fold rip_connection into rip_interface? - - FIXME: propagation of metric=infinity into main routing table may or may not be good idea. */ /** - * DOC: Routing Information Protocol + * DOC: Routing Information Protocol (RIP) + * + * The RIP protocol is implemented in two files: |rip.c| containing the protocol + * logic, route management and the protocol glue with BIRD core, and |packets.c| + * handling RIP packet processing, RX, TX and protocol sockets. + * + * Each instance of RIP is described by a structure &rip_proto, which contains + * an internal RIP routing table, a list of protocol interfaces and the main + * timer responsible for RIP routing table cleanup. + * + * RIP internal routing table contains incoming and outgoing routes. For each + * network (represented by structure &rip_entry) there is one outgoing route + * stored directly in &rip_entry and an one-way linked list of incoming routes + * (structures &rip_rte). The list contains incoming routes from different RIP + * neighbors, but only routes with the lowest metric are stored (i.e., all + * stored incoming routes have the same metric). + * + * Note that RIP itself does not select outgoing route, that is done by the core + * routing table. When a new incoming route is received, it is propagated to the + * RIP table by rip_update_rte() and possibly stored in the list of incoming + * routes. Then the change may be propagated to the core by rip_announce_rte(). + * The core selects the best route and propagate it to RIP by rip_rt_notify(), + * which updates outgoing route part of &rip_entry and possibly triggers route + * propagation by rip_trigger_update(). * - * RIP is a pretty simple protocol, so about a half of its code is interface - * with the core. + * RIP interfaces are represented by structures &rip_iface. A RIP interface + * contains a per-interface socket, a list of associated neighbors, interface + * configuration, and state information related to scheduled interface events + * and running update sessions. RIP interfaces are added and removed based on + * core interface notifications. * - * We maintain our own linked list of &rip_entry structures -- it serves - * as our small routing table. RIP never adds to this linked list upon - * packet reception; instead, it lets the core know about data from the packet - * and waits for the core to call rip_rt_notify(). + * There are two RIP interface events - regular updates and triggered updates. + * Both are managed from the RIP interface timer (rip_iface_timer()). Regular + * updates are called at fixed interval and propagate the whole routing table, + * while triggered updates are scheduled by rip_trigger_update() due to some + * routing table change and propagate only the routes modified since the time + * they were scheduled. There are also unicast-destined requested updates, but + * these are sent directly as a reaction to received RIP request message. The + * update session is started by rip_send_table(). There may be at most one + * active update session per interface, as the associated state (including the + * fib iterator) is stored directly in &rip_iface structure. * - * Within rip_tx(), the list is - * walked and a packet is generated using rip_tx_prepare(). This gets - * tricky because we may need to send more than one packet to one - * destination. Struct &rip_connection is used to hold context information such as how - * many of &rip_entry's we have already sent and it's also used to protect - * against two concurrent sends to one destination. Each &rip_interface has - * at most one &rip_connection. + * RIP neighbors are represented by structures &rip_neighbor. Compared to + * neighbor handling in other routing protocols, RIP does not have explicit + * neighbor discovery and adjacency maintenance, which makes the &rip_neighbor + * related code a bit peculiar. RIP neighbors are interlinked with core neighbor + * structures (&neighbor) and use core neighbor notifications to ensure that RIP + * neighbors are timely removed. RIP neighbors are added based on received route + * notifications and removed based on core neighbor and RIP interface events. * - * We are not going to honor requests for sending part of - * routing table. That would need to turn split horizon off etc. + * RIP neighbors are linked by RIP routes and use counter to track the number of + * associated routes, but when these RIP routes timeout, associated RIP neighbor + * is still alive (with zero counter). When RIP neighbor is removed but still + * has some associated routes, it is not freed, just changed to detached state + * (core neighbors and RIP ifaces are unlinked), then during the main timer + * cleanup phase the associated routes are removed and the &rip_neighbor + * structure is finally freed. * - * About triggered updates, RFC says: when a triggered update was sent, - * don't send a new one for something between 1 and 5 seconds (and send one - * after that). We do something else: each 5 seconds, - * we look for any changed routes and broadcast them. + * Supported standards: + * - RFC 1058 - RIPv1 + * - RFC 2453 - RIPv2 + * - RFC 2080 - RIPng + * - RFC 4822 - RIP cryptographic authentication */ -#undef LOCAL_DEBUG -#define LOCAL_DEBUG 1 - -#include "nest/bird.h" -#include "nest/iface.h" -#include "nest/protocol.h" -#include "nest/route.h" -#include "lib/socket.h" -#include "lib/resource.h" -#include "lib/lists.h" -#include "lib/timer.h" -#include "lib/string.h" - +#include <stdlib.h> #include "rip.h" -#define P ((struct rip_proto *) p) -#define P_CF ((struct rip_proto_config *)p->cf) -#undef TRACE -#define TRACE(level, msg, args...) do { if (p->debug & level) { log(L_TRACE "%s: " msg, p->name , ## args); } } while(0) +static inline void rip_lock_neighbor(struct rip_neighbor *n); +static inline void rip_unlock_neighbor(struct rip_neighbor *n); +static inline int rip_iface_link_up(struct rip_iface *ifa); +static inline void rip_kick_timer(struct rip_proto *p); +static inline void rip_iface_kick_timer(struct rip_iface *ifa); +static void rip_iface_timer(timer *timer); +static void rip_trigger_update(struct rip_proto *p); -static struct rip_interface *new_iface(struct proto *p, struct iface *new, unsigned long flags, struct iface_patt *patt); /* - * Output processing - * - * This part is responsible for getting packets out to the network. + * RIP routes */ static void -rip_tx_err( sock *s, int err ) +rip_init_entry(struct fib_node *fn) { - struct rip_connection *c = ((struct rip_interface *)(s->data))->busy; - struct proto *p = c->proto; - log( L_ERR "%s: Unexpected error at rip transmit: %M", p->name, err ); + // struct rip_entry *en = (void) *fn; + + const uint offset = OFFSETOF(struct rip_entry, routes); + memset((byte *)fn + offset, 0, sizeof(struct rip_entry) - offset); } -/* - * rip_tx_prepare: - * @e: rip entry that needs to be translated to form suitable for network - * @b: block to be filled - * - * Fill one rip block with info that needs to go to the network. Handle - * nexthop and split horizont correctly. (Next hop is ignored for IPv6, - * that could be fixed but it is not real problem). - */ -static int -rip_tx_prepare(struct proto *p, struct rip_block *b, struct rip_entry *e, struct rip_interface *rif, int pos ) +static struct rip_rte * +rip_add_rte(struct rip_proto *p, struct rip_rte **rp, struct rip_rte *src) { - int metric; - DBG( "." ); - b->tag = htons( e->tag ); - b->network = e->n.prefix; - metric = e->metric; - if (neigh_connected_to(p, &e->whotoldme, rif->iface)) { - DBG( "(split horizon)" ); - metric = P_CF->infinity; - } -#ifndef IPV6 - b->family = htons( 2 ); /* AF_INET */ - b->netmask = ipa_mkmask( e->n.pxlen ); - ipa_hton( b->netmask ); + struct rip_rte *rt = sl_alloc(p->rte_slab); - if (neigh_connected_to(p, &e->nexthop, rif->iface)) - b->nexthop = e->nexthop; - else - b->nexthop = IPA_NONE; - ipa_hton( b->nexthop ); - b->metric = htonl( metric ); -#else - b->pxlen = e->n.pxlen; - b->metric = metric; /* it is u8 */ -#endif + memcpy(rt, src, sizeof(struct rip_rte)); + rt->next = *rp; + *rp = rt; - ipa_hton( b->network ); + rip_lock_neighbor(rt->from); - return pos+1; + return rt; } -/* - * rip_tx - send one rip packet to the network +static inline void +rip_remove_rte(struct rip_proto *p, struct rip_rte **rp) +{ + struct rip_rte *rt = *rp; + + rip_unlock_neighbor(rt->from); + + *rp = rt->next; + sl_free(p->rte_slab, rt); +} + +static inline int rip_same_rte(struct rip_rte *a, struct rip_rte *b) +{ return a->metric == b->metric && a->tag == b->tag && ipa_equal(a->next_hop, b->next_hop); } + +static inline int rip_valid_rte(struct rip_rte *rt) +{ return rt->from->ifa != NULL; } + +/** + * rip_announce_rte - announce route from RIP routing table to the core + * @p: RIP instance + * @en: related network + * + * The function takes a list of incoming routes from @en, prepare appropriate + * &rte for the core and propagate it by rte_update(). */ static void -rip_tx( sock *s ) +rip_announce_rte(struct rip_proto *p, struct rip_entry *en) { - struct rip_interface *rif = s->data; - struct rip_connection *c = rif->busy; - struct proto *p = c->proto; - struct rip_packet *packet = (void *) s->tbuf; - int i, packetlen; - int maxi, nullupdate = 1; - - DBG( "Sending to %I\n", s->daddr ); - do { - - if (c->done) - goto done; - - DBG( "Preparing packet to send: " ); - - packet->heading.command = RIPCMD_RESPONSE; -#ifndef IPV6 - packet->heading.version = RIP_V2; -#else - packet->heading.version = RIP_NG; -#endif - packet->heading.unused = 0; - - i = !!P_CF->authtype; -#ifndef IPV6 - maxi = ((P_CF->authtype == AT_MD5) ? PACKET_MD5_MAX : PACKET_MAX); -#else - maxi = 5; /* We need to have at least reserve of one at end of packet */ -#endif - - FIB_ITERATE_START(&P->rtable, &c->iter, z) { - struct rip_entry *e = (struct rip_entry *) z; - - if (!rif->triggered || (!(e->updated < now-2))) { /* FIXME: Should be probably 1 or some different algorithm */ - nullupdate = 0; - i = rip_tx_prepare( p, packet->block + i, e, rif, i ); - if (i >= maxi) { - FIB_ITERATE_PUT(&c->iter, z); - goto break_loop; - } - } - } FIB_ITERATE_END(z); - c->done = 1; + struct rip_rte *rt = en->routes; + + /* Find first valid rte */ + while (rt && !rip_valid_rte(rt)) + rt = rt->next; + + if (rt) + { + /* Update */ + net *n = net_get(p->p.table, en->n.prefix, en->n.pxlen); - break_loop: + rta a0 = { + .src = p->p.main_source, + .source = RTS_RIP, + .scope = SCOPE_UNIVERSE, + .cast = RTC_UNICAST + }; - packetlen = rip_outgoing_authentication(p, (void *) &packet->block[0], packet, i); + u8 rt_metric = rt->metric; + u16 rt_tag = rt->tag; + struct rip_rte *rt2 = rt->next; + + /* Find second valid rte */ + while (rt2 && !rip_valid_rte(rt2)) + rt2 = rt2->next; + + if (p->ecmp && rt2) + { + /* ECMP route */ + struct mpnh *nhs = NULL; + struct mpnh **nhp = &nhs; + int num = 0; - DBG( ", sending %d blocks, ", i ); - if (nullupdate) { - DBG( "not sending NULL update\n" ); - c->done = 1; - goto done; + for (rt = en->routes; rt && (num < p->ecmp); rt = rt->next) + { + if (!rip_valid_rte(rt)) + continue; + + struct mpnh *nh = alloca(sizeof(struct mpnh)); + nh->gw = rt->next_hop; + nh->iface = rt->from->nbr->iface; + nh->weight = rt->from->ifa->cf->ecmp_weight; + nh->next = NULL; + *nhp = nh; + nhp = &(nh->next); + num++; + + if (rt->tag != rt_tag) + rt_tag = 0; + } + + a0.dest = RTD_MULTIPATH; + a0.nexthops = nhs; } - if (ipa_nonzero(c->daddr)) - i = sk_send_to( s, packetlen, c->daddr, c->dport ); else - i = sk_send( s, packetlen ); - - DBG( "it wants more\n" ); - - } while (i>0); - - if (i<0) rip_tx_err( s, i ); - DBG( "blocked\n" ); - return; - -done: - DBG( "Looks like I'm" ); - c->rif->busy = NULL; - rem_node(NODE c); - mb_free(c); - DBG( " done\n" ); - return; -} + { + /* Unipath route */ + a0.dest = RTD_ROUTER; + a0.gw = rt->next_hop; + a0.iface = rt->from->nbr->iface; + a0.from = rt->from->nbr->addr; + } -/* - * rip_sendto - send whole routing table to selected destination - * @rif: interface to use. Notice that we lock interface so that at - * most one send to one interface is done. - */ -static void -rip_sendto( struct proto *p, ip_addr daddr, int dport, struct rip_interface *rif ) -{ - struct iface *iface = rif->iface; - struct rip_connection *c; - static int num = 0; + rta *a = rta_lookup(&a0); + rte *e = rte_get_temp(a); - if (rif->busy) { - log (L_WARN "%s: Interface %s is much too slow, dropping request", p->name, iface->name); - return; + e->u.rip.from = a0.iface; + e->u.rip.metric = rt_metric; + e->u.rip.tag = rt_tag; + + e->net = n; + e->pflags = 0; + + rte_update(&p->p, n, e); } - c = mb_alloc( p->pool, sizeof( struct rip_connection )); - rif->busy = c; - - c->addr = daddr; - c->proto = p; - c->num = num++; - c->rif = rif; - - c->dport = dport; - c->daddr = daddr; - if (c->rif->sock->data != rif) - bug("not enough send magic"); - - c->done = 0; - FIB_ITERATE_INIT( &c->iter, &P->rtable ); - add_head( &P->connections, NODE c ); - if (ipa_nonzero(daddr)) - TRACE(D_PACKETS, "Sending my routing table to %I:%d on %s", daddr, dport, rif->iface->name ); else - TRACE(D_PACKETS, "Broadcasting routing table to %s", rif->iface->name ); - - rip_tx(c->rif->sock); + { + /* Withdraw */ + net *n = net_find(p->p.table, en->n.prefix, en->n.pxlen); + rte_update(&p->p, n, NULL); + } } -static struct rip_interface* -find_interface(struct proto *p, struct iface *what) +/** + * rip_update_rte - enter a route update to RIP routing table + * @p: RIP instance + * @prefix: network prefix + * @pxlen: network prefix length + * @new: a &rip_rte representing the new route + * + * The function is called by the RIP packet processing code whenever it receives + * a reachable route. The appropriate routing table entry is found and the list + * of incoming routes is updated. Eventually, the change is also propagated to + * the core by rip_announce_rte(). Note that for unreachable routes, + * rip_withdraw_rte() should be called instead of rip_update_rte(). + */ +void +rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte *new) { - struct rip_interface *i; + struct rip_entry *en = fib_get(&p->rtable, prefix, pxlen); + struct rip_rte *rt, **rp; + int changed = 0; + + /* If the new route is better, remove all current routes */ + if (en->routes && new->metric < en->routes->metric) + while (en->routes) + rip_remove_rte(p, &en->routes); + + /* Find the old route (also set rp for later) */ + for (rp = &en->routes; rt = *rp; rp = &rt->next) + if (rt->from == new->from) + { + if (rip_same_rte(rt, new)) + { + rt->expires = new->expires; + return; + } - WALK_LIST (i, P->interfaces) - if (i->iface == what) - return i; - return NULL; + /* Remove the old route */ + rip_remove_rte(p, rp); + changed = 1; + break; + } + + /* If the new route is optimal, add it to the list */ + if (!en->routes || new->metric == en->routes->metric) + { + rt = rip_add_rte(p, rp, new); + changed = 1; + } + + /* Announce change if on relevant position (the first or any for ECMP) */ + if (changed && (rp == &en->routes || p->ecmp)) + rip_announce_rte(p, en); } -/* - * Input processing +/** + * rip_withdraw_rte - enter a route withdraw to RIP routing table + * @p: RIP instance + * @prefix: network prefix + * @pxlen: network prefix length + * @from: a &rip_neighbor propagating the withdraw * - * This part is responsible for any updates that come from network + * The function is called by the RIP packet processing code whenever it receives + * an unreachable route. The incoming route for given network from nbr @from is + * removed. Eventually, the change is also propagated by rip_announce_rte(). */ +void +rip_withdraw_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_neighbor *from) +{ + struct rip_entry *en = fib_find(&p->rtable, prefix, pxlen); + struct rip_rte *rt, **rp; -static int rip_rte_better(struct rte *new, struct rte *old); + if (!en) + return; -static void -rip_rte_update_if_better(rtable *tab, net *net, struct proto *p, rte *new) -{ - rte *old; + /* Find the old route */ + for (rp = &en->routes; rt = *rp; rp = &rt->next) + if (rt->from == from) + break; - old = rte_find(net, p->main_source); - if (!old || rip_rte_better(new, old) || - (ipa_equal(old->attrs->from, new->attrs->from) && - (old->u.rip.metric != new->u.rip.metric)) ) - rte_update(p, net, new); - else - rte_free(new); + if (!rt) + return; + + /* Remove the old route */ + rip_remove_rte(p, rp); + + /* Announce change if on relevant position */ + if (rp == &en->routes || p->ecmp) + rip_announce_rte(p, en); } /* - * advertise_entry - let main routing table know about our new entry - * @b: entry in network format - * - * This basically translates @b to format used by bird core and feeds - * bird core with this route. + * rip_rt_notify - core tells us about new route, so store + * it into our data structures. */ static void -advertise_entry( struct proto *p, struct rip_block *b, ip_addr whotoldme, struct iface *iface ) +rip_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, struct rte *new, + struct rte *old UNUSED, struct ea_list *attrs) { - rta *a, A; - rte *r; - net *n; - neighbor *neighbor; - struct rip_interface *rif; - int pxlen; - - bzero(&A, sizeof(A)); - A.src= p->main_source; - A.source = RTS_RIP; - A.scope = SCOPE_UNIVERSE; - A.cast = RTC_UNICAST; - A.dest = RTD_ROUTER; - A.flags = 0; -#ifndef IPV6 - A.gw = ipa_nonzero(b->nexthop) ? b->nexthop : whotoldme; - pxlen = ipa_masklen(b->netmask); -#else - /* FIXME: next hop is in other packet for v6 */ - A.gw = whotoldme; - pxlen = b->pxlen; -#endif - A.from = whotoldme; - - /* No need to look if destination looks valid - ie not net 0 or 127 -- core will do for us. */ - - neighbor = neigh_find2( p, &A.gw, iface, 0 ); - if (!neighbor) { - log( L_REMOTE "%s: %I asked me to route %I/%d using not-neighbor %I.", p->name, A.from, b->network, pxlen, A.gw ); - return; - } - if (neighbor->scope == SCOPE_HOST) { - DBG("Self-destined route, ignoring.\n"); - return; + struct rip_proto *p = (struct rip_proto *) P; + struct rip_entry *en; + int old_metric; + + if (new) + { + /* Update */ + u32 rt_metric = ea_get_int(attrs, EA_RIP_METRIC, 1); + u32 rt_tag = ea_get_int(attrs, EA_RIP_TAG, 0); + + if (rt_metric > p->infinity) + { + log(L_WARN "%s: Invalid rip_metric value %u for route %I/%d", + p->p.name, rt_metric, net->n.prefix, net->n.pxlen); + rt_metric = p->infinity; + } + + if (rt_tag > 0xffff) + { + log(L_WARN "%s: Invalid rip_tag value %u for route %I/%d", + p->p.name, rt_tag, net->n.prefix, net->n.pxlen); + rt_metric = p->infinity; + rt_tag = 0; + } + + /* + * Note that we accept exported routes with infinity metric (this could + * happen if rip_metric is modified in filters). Such entry has infinity + * metric but is RIP_ENTRY_VALID and therefore is not subject to garbage + * collection. + */ + + en = fib_get(&p->rtable, &net->n.prefix, net->n.pxlen); + + old_metric = en->valid ? en->metric : -1; + + en->valid = RIP_ENTRY_VALID; + en->metric = rt_metric; + en->tag = rt_tag; + en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL; + en->iface = new->attrs->iface; + en->next_hop = new->attrs->gw; } + else + { + /* Withdraw */ + en = fib_find(&p->rtable, &net->n.prefix, net->n.pxlen); - A.iface = neighbor->iface; - if (!(rif = neighbor->data)) { - rif = neighbor->data = find_interface(p, A.iface); + if (!en || en->valid != RIP_ENTRY_VALID) + return; + + old_metric = en->metric; + + en->valid = RIP_ENTRY_STALE; + en->metric = p->infinity; + en->tag = 0; + en->from = NULL; + en->iface = NULL; + en->next_hop = IPA_NONE; } - if (!rif) - bug("Route packet using unknown interface? No."); - - /* set to: interface of nexthop */ - a = rta_lookup(&A); - if (pxlen==-1) { - log( L_REMOTE "%s: %I gave me invalid pxlen/netmask for %I.", p->name, A.from, b->network ); - return; + + /* Activate triggered updates */ + if (en->metric != old_metric) + { + en->changed = now; + rip_trigger_update(p); } - n = net_get( p->table, b->network, pxlen ); - r = rte_get_temp(a); -#ifndef IPV6 - r->u.rip.metric = ntohl(b->metric) + rif->metric; -#else - r->u.rip.metric = b->metric + rif->metric; -#endif - - r->u.rip.entry = NULL; - if (r->u.rip.metric > P_CF->infinity) r->u.rip.metric = P_CF->infinity; - r->u.rip.tag = ntohl(b->tag); - r->net = n; - r->pflags = 0; /* Here go my flags */ - rip_rte_update_if_better( p->table, n, p, r ); - DBG( "done\n" ); } + /* - * process_block - do some basic check and pass block to advertise_entry + * RIP neighbors */ + +struct rip_neighbor * +rip_get_neighbor(struct rip_proto *p, ip_addr *a, struct rip_iface *ifa) +{ + neighbor *nbr = neigh_find2(&p->p, a, ifa->iface, 0); + + if (!nbr || (nbr->scope == SCOPE_HOST) || !rip_iface_link_up(ifa)) + return NULL; + + if (nbr->data) + return nbr->data; + + TRACE(D_EVENTS, "New neighbor %I on %s", *a, ifa->iface->name); + + struct rip_neighbor *n = mb_allocz(p->p.pool, sizeof(struct rip_neighbor)); + n->ifa = ifa; + n->nbr = nbr; + nbr->data = n; + n->csn = nbr->aux; + + add_tail(&ifa->neigh_list, NODE n); + + return n; +} + static void -process_block( struct proto *p, struct rip_block *block, ip_addr whotoldme, struct iface *iface ) +rip_remove_neighbor(struct rip_proto *p, struct rip_neighbor *n) { - int metric, pxlen; - -#ifndef IPV6 - metric = ntohl( block->metric ); - pxlen = ipa_masklen(block->netmask); -#else - metric = block->metric; - pxlen = block->pxlen; -#endif - ip_addr network = block->network; - - CHK_MAGIC; - - TRACE(D_ROUTES, "block: %I tells me: %I/%d available, metric %d... ", - whotoldme, network, pxlen, metric ); - - if ((!metric) || (metric > P_CF->infinity)) { -#ifdef IPV6 /* Someone is sending us nexthop and we are ignoring it */ - if (metric == 0xff) - { DBG( "IPv6 nexthop ignored" ); return; } -#endif - log( L_WARN "%s: Got metric %d from %I", p->name, metric, whotoldme ); - return; - } + neighbor *nbr = n->nbr; + + TRACE(D_EVENTS, "Removing neighbor %I on %s", nbr->addr, nbr->iface->name); - advertise_entry( p, block, whotoldme, iface ); + rem_node(NODE n); + n->ifa = NULL; + n->nbr = NULL; + nbr->data = NULL; + nbr->aux = n->csn; + + rfree(n->bfd_req); + n->bfd_req = NULL; + n->last_seen = 0; + + if (!n->uc) + mb_free(n); + + /* Related routes are removed in rip_timer() */ + rip_kick_timer(p); } -#define BAD( x ) { log( L_REMOTE "%s: " x, p->name ); return 1; } +static inline void +rip_lock_neighbor(struct rip_neighbor *n) +{ + n->uc++; +} -/* - * rip_process_packet - this is main routine for incoming packets. - */ -static int -rip_process_packet( struct proto *p, struct rip_packet *packet, int num, ip_addr whotoldme, int port, struct iface *iface ) +static inline void +rip_unlock_neighbor(struct rip_neighbor *n) { - int i; - int authenticated = 0; - neighbor *neighbor; + n->uc--; + + if (!n->nbr && !n->uc) + mb_free(n); +} + +static void +rip_neigh_notify(struct neighbor *nbr) +{ + struct rip_proto *p = (struct rip_proto *) nbr->proto; + struct rip_neighbor *n = nbr->data; + + if (!n) + return; + + /* + * We assume that rip_neigh_notify() is called before rip_if_notify() for + * IF_CHANGE_DOWN and therefore n->ifa is still valid. We have no such + * ordering assumption for IF_CHANGE_LINK, so we test link state of the + * underlying iface instead of just rip_iface state. + */ + if ((nbr->scope <= 0) || !rip_iface_link_up(n->ifa)) + rip_remove_neighbor(p, n); +} + +static void +rip_bfd_notify(struct bfd_request *req) +{ + struct rip_neighbor *n = req->data; + struct rip_proto *p = n->ifa->rip; - switch( packet->heading.version ) { - case RIP_V1: DBG( "Rip1: " ); break; - case RIP_V2: DBG( "Rip2: " ); break; - default: BAD( "Unknown version" ); + if (req->down) + { + TRACE(D_EVENTS, "BFD session down for nbr %I on %s", + n->nbr->addr, n->ifa->iface->name); + rip_remove_neighbor(p, n); } +} + +void +rip_update_bfd(struct rip_proto *p, struct rip_neighbor *n) +{ + int use_bfd = n->ifa->cf->bfd && n->last_seen; - switch( packet->heading.command ) { - case RIPCMD_REQUEST: DBG( "Asked to send my routing table\n" ); - if (P_CF->honor == HO_NEVER) - BAD( "They asked me to send routing table, but I was told not to do it" ); - - if ((P_CF->honor == HO_NEIGHBOR) && (!neigh_find2( p, &whotoldme, iface, 0 ))) - BAD( "They asked me to send routing table, but he is not my neighbor" ); - rip_sendto( p, whotoldme, port, HEAD(P->interfaces) ); /* no broadcast */ - break; - case RIPCMD_RESPONSE: DBG( "*** Rtable from %I\n", whotoldme ); - if (port != P_CF->port) { - log( L_REMOTE "%s: %I send me routing info from port %d", p->name, whotoldme, port ); - return 1; - } - - if (!(neighbor = neigh_find2( p, &whotoldme, iface, 0 )) || neighbor->scope == SCOPE_HOST) { - log( L_REMOTE "%s: %I send me routing info but he is not my neighbor", p->name, whotoldme ); - return 0; - } - - for (i=0; i<num; i++) { - struct rip_block *block = &packet->block[i]; -#ifndef IPV6 - /* Authentication is not defined for v6 */ - if (block->family == 0xffff) { - if (i) - continue; /* md5 tail has this family */ - if (rip_incoming_authentication(p, (void *) block, packet, num, whotoldme)) - BAD( "Authentication failed" ); - authenticated = 1; - continue; - } -#endif - if ((!authenticated) && (P_CF->authtype != AT_NONE)) - BAD( "Packet is not authenticated and it should be" ); - ipa_ntoh( block->network ); -#ifndef IPV6 - ipa_ntoh( block->netmask ); - ipa_ntoh( block->nexthop ); - if (packet->heading.version == RIP_V1) /* FIXME (nonurgent): switch to disable this? */ - block->netmask = ip4_class_mask(ipa_to_ip4(block->network)); -#endif - process_block( p, block, whotoldme, iface ); - } - break; - case RIPCMD_TRACEON: - case RIPCMD_TRACEOFF: BAD( "I was asked for traceon/traceoff" ); - case 5: BAD( "Some Sun extension around here" ); - default: BAD( "Unknown command" ); + if (use_bfd && !n->bfd_req) + { + /* + * For RIPv2, use the same address as rip_open_socket(). For RIPng, neighbor + * should contain an address from the same prefix, thus also link-local. It + * may cause problems if two link-local addresses are assigned to one iface. + */ + ip_addr saddr = rip_is_v2(p) ? n->ifa->sk->saddr : n->nbr->ifa->ip; + n->bfd_req = bfd_request_session(p->p.pool, n->nbr->addr, saddr, + n->nbr->iface, rip_bfd_notify, n); } - return 0; + if (!use_bfd && n->bfd_req) + { + rfree(n->bfd_req); + n->bfd_req = NULL; + } } + /* - * rip_rx - Receive hook: do basic checks and pass packet to rip_process_packet + * RIP interfaces */ -static int -rip_rx(sock *s, int size) + +static void +rip_iface_start(struct rip_iface *ifa) { - struct rip_interface *i = s->data; - struct proto *p = i->proto; - struct iface *iface = NULL; - int num; + struct rip_proto *p = ifa->rip; - /* In non-listening mode, just ignore packet */ - if (i->mode & IM_NOLISTEN) - return 1; + TRACE(D_EVENTS, "Starting interface %s", ifa->iface->name); -#ifdef IPV6 - if (! i->iface || s->lifindex != i->iface->index) - return 1; + ifa->next_regular = now + (random() % ifa->cf->update_time) + 1; + ifa->next_triggered = now; /* Available immediately */ + ifa->want_triggered = 1; /* All routes in triggered update */ + tm_start(ifa->timer, 1); /* Or 100 ms */ + ifa->up = 1; - iface = i->iface; -#endif + if (!ifa->cf->passive) + rip_send_request(ifa->rip, ifa); +} - if (i->check_ttl && (s->rcv_ttl < 255)) - { - log( L_REMOTE "%s: Discarding packet with TTL %d (< 255) from %I on %s", - p->name, s->rcv_ttl, s->faddr, i->iface->name); - return 1; - } +static void +rip_iface_stop(struct rip_iface *ifa) +{ + struct rip_proto *p = ifa->rip; + struct rip_neighbor *n; + TRACE(D_EVENTS, "Stopping interface %s", ifa->iface->name); - CHK_MAGIC; - DBG( "RIP: message came: %d bytes from %I via %s\n", size, s->faddr, i->iface ? i->iface->name : "(dummy)" ); - size -= sizeof( struct rip_packet_heading ); - if (size < 0) BAD( "Too small packet" ); - if (size % sizeof( struct rip_block )) BAD( "Odd sized packet" ); - num = size / sizeof( struct rip_block ); - if (num>PACKET_MAX) BAD( "Too many blocks" ); + rip_reset_tx_session(p, ifa); - if (ipa_equal(i->iface->addr->ip, s->faddr)) { - DBG("My own packet\n"); - return 1; - } + WALK_LIST_FIRST(n, ifa->neigh_list) + rip_remove_neighbor(p, n); - rip_process_packet( p, (struct rip_packet *) s->rbuf, num, s->faddr, s->fport, iface ); - return 1; + tm_stop(ifa->timer); + ifa->up = 0; } -/* - * Interface to BIRD core - */ +static inline int +rip_iface_link_up(struct rip_iface *ifa) +{ + return !ifa->cf->check_link || (ifa->iface->flags & IF_LINK_UP); +} static void -rip_dump_entry( struct rip_entry *e ) +rip_iface_update_state(struct rip_iface *ifa) { - debug( "%I told me %d/%d ago: to %I/%d go via %I, metric %d ", - e->whotoldme, e->updated-now, e->changed-now, e->n.prefix, e->n.pxlen, e->nexthop, e->metric ); - debug( "\n" ); -} + int up = ifa->sk && rip_iface_link_up(ifa); -/** - * rip_timer - * @t: timer - * - * Broadcast routing tables periodically (using rip_tx) and kill - * routes that are too old. RIP keeps a list of its own entries present - * in the core table by a linked list (functions rip_rte_insert() and - * rip_rte_delete() are responsible for that), it walks this list in the timer - * and in case an entry is too old, it is discarded. - */ + if (up == ifa->up) + return; + + if (up) + rip_iface_start(ifa); + else + rip_iface_stop(ifa); +} static void -rip_timer(timer *t) +rip_iface_update_buffers(struct rip_iface *ifa) { - struct proto *p = t->data; - struct fib_node *e, *et; + if (!ifa->sk) + return; - CHK_MAGIC; - DBG( "RIP: tick tock\n" ); - - WALK_LIST_DELSAFE( e, et, P->garbage ) { - rte *rte; - rte = SKIP_BACK( struct rte, u.rip.garbage, e ); + uint rbsize = ifa->cf->rx_buffer ?: ifa->iface->mtu; + uint tbsize = ifa->cf->tx_length ?: ifa->iface->mtu; + rbsize = MAX(rbsize, tbsize); - CHK_MAGIC; + sk_set_rbsize(ifa->sk, rbsize); + sk_set_tbsize(ifa->sk, tbsize); - DBG( "Garbage: (%p)", rte ); rte_dump( rte ); + uint headers = (rip_is_v2(ifa->rip) ? IP4_HEADER_LENGTH : IP6_HEADER_LENGTH) + UDP_HEADER_LENGTH; + ifa->tx_plen = tbsize - headers; - if (now - rte->lastmod > P_CF->timeout_time) { - TRACE(D_EVENTS, "entry is too old: %I", rte->net->n.prefix ); - if (rte->u.rip.entry) { - rte->u.rip.entry->metric = P_CF->infinity; - rte->u.rip.metric = P_CF->infinity; - } - } + if (ifa->cf->auth_type == RIP_AUTH_CRYPTO) + ifa->tx_plen -= RIP_AUTH_TAIL_LENGTH; +} - if (now - rte->lastmod > P_CF->garbage_time) { - TRACE(D_EVENTS, "entry is much too old: %I", rte->net->n.prefix ); - rte_discard(p->table, rte); - } - } +static inline void +rip_iface_update_bfd(struct rip_iface *ifa) +{ + struct rip_proto *p = ifa->rip; + struct rip_neighbor *n; + + WALK_LIST(n, ifa->neigh_list) + rip_update_bfd(p, n); +} + + +static void +rip_iface_locked(struct object_lock *lock) +{ + struct rip_iface *ifa = lock->data; + struct rip_proto *p = ifa->rip; - DBG( "RIP: Broadcasting routing tables\n" ); + if (!rip_open_socket(ifa)) { - struct rip_interface *rif; + log(L_ERR "%s: Cannot open socket for %s", p->p.name, ifa->iface->name); + return; + } - if ( P_CF->period > 2 ) { /* Bring some randomness into sending times */ - if (! (P->tx_count % P_CF->period)) P->rnd_count = random_u32() % 2; - } else P->rnd_count = P->tx_count % P_CF->period; + rip_iface_update_buffers(ifa); + rip_iface_update_state(ifa); +} - WALK_LIST( rif, P->interfaces ) { - struct iface *iface = rif->iface; - if (!iface) continue; - if (rif->mode & IM_QUIET) continue; - if (!(iface->flags & IF_UP)) continue; - rif->triggered = P->rnd_count; +static struct rip_iface * +rip_find_iface(struct rip_proto *p, struct iface *what) +{ + struct rip_iface *ifa; - rip_sendto( p, IPA_NONE, 0, rif ); - } - P->tx_count++; - P->rnd_count--; - } + WALK_LIST(ifa, p->iface_list) + if (ifa->iface == what) + return ifa; - DBG( "RIP: tick tock done\n" ); + return NULL; } -/* - * rip_start - initialize instance of rip - */ -static int -rip_start(struct proto *p) +static void +rip_add_iface(struct rip_proto *p, struct iface *iface, struct rip_iface_config *ic) { - struct rip_interface *rif; - DBG( "RIP: starting instance...\n" ); - - ASSERT(sizeof(struct rip_packet_heading) == 4); - ASSERT(sizeof(struct rip_block) == 20); - ASSERT(sizeof(struct rip_block_auth) == 20); - -#ifdef LOCAL_DEBUG - P->magic = RIP_MAGIC; -#endif - fib_init( &P->rtable, p->pool, sizeof( struct rip_entry ), 0, NULL ); - init_list( &P->connections ); - init_list( &P->garbage ); - init_list( &P->interfaces ); - P->timer = tm_new( p->pool ); - P->timer->data = p; - P->timer->recurrent = 1; - P->timer->hook = rip_timer; - tm_start( P->timer, 2 ); - rif = new_iface(p, NULL, 0, NULL); /* Initialize dummy interface */ - add_head( &P->interfaces, NODE rif ); - CHK_MAGIC; - - DBG( "RIP: ...done\n"); - return PS_UP; + struct rip_iface *ifa; + + TRACE(D_EVENTS, "Adding interface %s", iface->name); + + ifa = mb_allocz(p->p.pool, sizeof(struct rip_iface)); + ifa->rip = p; + ifa->iface = iface; + ifa->cf = ic; + + if (ipa_nonzero(ic->address)) + ifa->addr = ic->address; + else if (ic->mode == RIP_IM_MULTICAST) + ifa->addr = rip_is_v2(p) ? IP4_RIP_ROUTERS : IP6_RIP_ROUTERS; + else /* Broadcast */ + ifa->addr = iface->addr->brd; + + init_list(&ifa->neigh_list); + + add_tail(&p->iface_list, NODE ifa); + + ifa->timer = tm_new_set(p->p.pool, rip_iface_timer, ifa, 0, 0); + + struct object_lock *lock = olock_new(p->p.pool); + lock->type = OBJLOCK_UDP; + lock->port = ic->port; + lock->iface = iface; + lock->data = ifa; + lock->hook = rip_iface_locked; + ifa->lock = lock; + + olock_acquire(lock); } static void -rip_dump(struct proto *p) +rip_remove_iface(struct rip_proto *p, struct rip_iface *ifa) { - int i; - node *w; - struct rip_interface *rif; + rip_iface_stop(ifa); - CHK_MAGIC; - WALK_LIST( w, P->connections ) { - struct rip_connection *n = (void *) w; - debug( "RIP: connection #%d: %I\n", n->num, n->addr ); - } - i = 0; - FIB_WALK( &P->rtable, e ) { - debug( "RIP: entry #%d: ", i++ ); - rip_dump_entry( (struct rip_entry *)e ); - } FIB_WALK_END; - i = 0; - WALK_LIST( rif, P->interfaces ) { - debug( "RIP: interface #%d: %s, %I, busy = %x\n", i++, rif->iface?rif->iface->name:"(dummy)", rif->sock->daddr, rif->busy ); - } + TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name); + + rem_node(NODE ifa); + + rfree(ifa->sk); + rfree(ifa->lock); + rfree(ifa->timer); + + mb_free(ifa); +} + +static int +rip_reconfigure_iface(struct rip_proto *p, struct rip_iface *ifa, struct rip_iface_config *new) +{ + struct rip_iface_config *old = ifa->cf; + + /* Change of these options would require to reset the iface socket */ + if ((new->mode != old->mode) || + (new->port != old->port) || + (new->tx_tos != old->tx_tos) || + (new->tx_priority != old->tx_priority) || + (new->ttl_security != old->ttl_security)) + return 0; + + TRACE(D_EVENTS, "Reconfiguring interface %s", ifa->iface->name); + + ifa->cf = new; + + if (ifa->next_regular > (now + new->update_time)) + ifa->next_regular = now + (random() % new->update_time) + 1; + + if ((new->tx_length != old->tx_length) || (new->rx_buffer != old->rx_buffer)) + rip_iface_update_buffers(ifa); + + if (new->check_link != old->check_link) + rip_iface_update_state(ifa); + + if (new->bfd != old->bfd) + rip_iface_update_bfd(ifa); + + if (ifa->up) + rip_iface_kick_timer(ifa); + + return 1; } static void -rip_get_route_info(rte *rte, byte *buf, ea_list *attrs) +rip_reconfigure_ifaces(struct rip_proto *p, struct rip_config *cf) { - eattr *metric = ea_find(attrs, EA_RIP_METRIC); - eattr *tag = ea_find(attrs, EA_RIP_TAG); + struct iface *iface; + + WALK_LIST(iface, iface_list) + { + if (! (iface->flags & IF_UP)) + continue; + + struct rip_iface *ifa = rip_find_iface(p, iface); + struct rip_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL); - buf += bsprintf(buf, " (%d/%d)", rte->pref, metric ? metric->u.data : 0); - if (tag && tag->u.data) - bsprintf(buf, " t%04x", tag->u.data); + if (ifa && ic) + { + if (rip_reconfigure_iface(p, ifa, ic)) + continue; + + /* Hard restart */ + log(L_INFO "%s: Restarting interface %s", p->p.name, ifa->iface->name); + rip_remove_iface(p, ifa); + rip_add_iface(p, iface, ic); + } + + if (ifa && !ic) + rip_remove_iface(p, ifa); + + if (!ifa && ic) + rip_add_iface(p, iface, ic); + } } static void -kill_iface(struct rip_interface *i) +rip_if_notify(struct proto *P, unsigned flags, struct iface *iface) { - DBG( "RIP: Interface %s disappeared\n", i->iface->name); - rfree(i->sock); - mb_free(i); + struct rip_proto *p = (void *) P; + struct rip_config *cf = (void *) P->cf; + + if (iface->flags & IF_IGNORE) + return; + + if (flags & IF_CHANGE_UP) + { + struct rip_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL); + + if (ic) + rip_add_iface(p, iface, ic); + + return; + } + + struct rip_iface *ifa = rip_find_iface(p, iface); + + if (!ifa) + return; + + if (flags & IF_CHANGE_DOWN) + { + rip_remove_iface(p, ifa); + return; + } + + if (flags & IF_CHANGE_MTU) + rip_iface_update_buffers(ifa); + + if (flags & IF_CHANGE_LINK) + rip_iface_update_state(ifa); } + +/* + * RIP timer events + */ + /** - * new_iface - * @p: myself - * @new: interface to be created or %NULL if we are creating a magic - * socket. The magic socket is used for listening and also for - * sending requested responses. - * @flags: interface flags - * @patt: pattern this interface matched, used for access to config options + * rip_timer - RIP main timer hook + * @t: timer * - * Create an interface structure and start listening on the interface. + * The RIP main timer is responsible for routing table maintenance. Invalid or + * expired routes (&rip_rte) are removed and garbage collection of stale routing + * table entries (&rip_entry) is done. Changes are propagated to core tables, + * route reload is also done here. Note that garbage collection uses a maximal + * GC time, while interfaces maintain an illusion of per-interface GC times in + * rip_send_response(). + * + * Keeping incoming routes and the selected outgoing route are two independent + * functions, therefore after garbage collection some entries now considered + * invalid (RIP_ENTRY_DUMMY) still may have non-empty list of incoming routes, + * while some valid entries (representing an outgoing route) may have that list + * empty. + * + * The main timer is not scheduled periodically but it uses the time of the + * current next event and the minimal interval of any possible event to compute + * the time of the next run. */ -static struct rip_interface * -new_iface(struct proto *p, struct iface *new, unsigned long flags, struct iface_patt *patt ) +static void +rip_timer(timer *t) { - struct rip_interface *rif; - struct rip_patt *PATT = (struct rip_patt *) patt; - - rif = mb_allocz(p->pool, sizeof( struct rip_interface )); - rif->iface = new; - rif->proto = p; - rif->busy = NULL; - if (PATT) { - rif->mode = PATT->mode; - rif->metric = PATT->metric; - rif->multicast = (!(PATT->mode & IM_BROADCAST)) && (flags & IF_MULTICAST); - rif->check_ttl = (PATT->ttl_security == 1); - } - /* lookup multicasts over unnumbered links - no: rip is not defined over unnumbered links */ - - if (rif->multicast) - DBG( "Doing multicasts!\n" ); - - rif->sock = sk_new( p->pool ); - rif->sock->type = SK_UDP; - rif->sock->sport = P_CF->port; - rif->sock->rx_hook = rip_rx; - rif->sock->data = rif; - rif->sock->rbsize = 10240; - rif->sock->iface = new; /* Automagically works for dummy interface */ - rif->sock->tbuf = mb_alloc( p->pool, sizeof( struct rip_packet )); - rif->sock->tx_hook = rip_tx; - rif->sock->err_hook = rip_tx_err; - rif->sock->daddr = IPA_NONE; - rif->sock->dport = P_CF->port; - if (new) + struct rip_proto *p = t->data; + struct rip_config *cf = (void *) (p->p.cf); + struct rip_iface *ifa; + struct rip_neighbor *n, *nn; + struct fib_iterator fit; + bird_clock_t next = now + MIN(cf->min_timeout_time, cf->max_garbage_time); + bird_clock_t expires = 0; + + TRACE(D_EVENTS, "Main timer fired"); + + FIB_ITERATE_INIT(&fit, &p->rtable); + + loop: + FIB_ITERATE_START(&p->rtable, &fit, node) + { + struct rip_entry *en = (struct rip_entry *) node; + struct rip_rte *rt, **rp; + int changed = 0; + + /* Checking received routes for timeout and for dead neighbors */ + for (rp = &en->routes; rt = *rp; /* rp = &rt->next */) { - rif->sock->tos = PATT->tx_tos; - rif->sock->priority = PATT->tx_priority; - rif->sock->ttl = PATT->ttl_security ? 255 : 1; - rif->sock->flags = SKF_LADDR_RX | (rif->check_ttl ? SKF_TTL_RX : 0); - } + if (!rip_valid_rte(rt) || (rt->expires <= now)) + { + rip_remove_rte(p, rp); + changed = 1; + continue; + } - if (new) { - if (new->addr->flags & IA_PEER) - log( L_WARN "%s: rip is not defined over unnumbered links", p->name ); - if (rif->multicast) { -#ifndef IPV6 - rif->sock->daddr = ipa_from_u32(0xe0000009); -#else - rif->sock->daddr = IP6_RIP_ROUTERS; -#endif - } else { - rif->sock->daddr = new->addr->brd; + next = MIN(next, rt->expires); + rp = &rt->next; } - } - if (!ipa_nonzero(rif->sock->daddr)) { - if (rif->iface) - log( L_WARN "%s: interface %s is too strange for me", p->name, rif->iface->name ); - } else { + /* Propagating eventual change */ + if (changed || p->rt_reload) + { + /* + * We have to restart the iteration because there may be a cascade of + * synchronous events rip_announce_rte() -> nest table change -> + * rip_rt_notify() -> p->rtable change, invalidating hidden variables. + */ + + FIB_ITERATE_PUT_NEXT(&fit, &p->rtable, node); + rip_announce_rte(p, en); + goto loop; + } - if (sk_open(rif->sock) < 0) - goto err; + /* Checking stale entries for garbage collection timeout */ + if (en->valid == RIP_ENTRY_STALE) + { + expires = en->changed + cf->max_garbage_time; - if (rif->multicast) + if (expires <= now) { - if (sk_setup_multicast(rif->sock) < 0) - goto err; - if (sk_join_group(rif->sock, rif->sock->daddr) < 0) - goto err; + // TRACE(D_EVENTS, "entry is too old: %I/%d", en->n.prefix, en->n.pxlen); + en->valid = 0; } - else + else + next = MIN(next, expires); + } + + /* Remove empty nodes */ + if (!en->valid && !en->routes) + { + FIB_ITERATE_PUT(&fit, node); + fib_delete(&p->rtable, node); + goto loop; + } + } + FIB_ITERATE_END(node); + + p->rt_reload = 0; + + /* Handling neighbor expiration */ + WALK_LIST(ifa, p->iface_list) + WALK_LIST_DELSAFE(n, nn, ifa->neigh_list) + if (n->last_seen) { - if (sk_setup_broadcast(rif->sock) < 0) - goto err; + expires = n->last_seen + n->ifa->cf->timeout_time; + + if (expires <= now) + rip_remove_neighbor(p, n); + else + next = MIN(next, expires); } - } - TRACE(D_EVENTS, "Listening on %s, port %d, mode %s (%I)", rif->iface ? rif->iface->name : "(dummy)", P_CF->port, rif->multicast ? "multicast" : "broadcast", rif->sock->daddr ); - - return rif; + tm_start(p->timer, MAX(next - now, 1)); +} + +static inline void +rip_kick_timer(struct rip_proto *p) +{ + if (p->timer->expires > (now + 1)) + tm_start(p->timer, 1); /* Or 100 ms */ +} - err: - sk_log_error(rif->sock, p->name); - log(L_ERR "%s: Cannot open socket for %s", p->name, rif->iface ? rif->iface->name : "(dummy)" ); - if (rif->iface) { - rfree(rif->sock); - mb_free(rif); - return NULL; +/** + * rip_iface_timer - RIP interface timer hook + * @t: timer + * + * RIP interface timers are responsible for scheduling both regular and + * triggered updates. Fixed, delay-independent period is used for regular + * updates, while minimal separating interval is enforced for triggered updates. + * The function also ensures that a new update is not started when the old one + * is still running. + */ +static void +rip_iface_timer(timer *t) +{ + struct rip_iface *ifa = t->data; + struct rip_proto *p = ifa->rip; + bird_clock_t period = ifa->cf->update_time; + + if (ifa->cf->passive) + return; + + TRACE(D_EVENTS, "Interface timer fired for %s", ifa->iface->name); + + if (ifa->tx_active) + { + if (now < (ifa->next_regular + period)) + { tm_start(ifa->timer, 1); return; } + + /* We are too late, reset is done by rip_send_table() */ + log(L_WARN "%s: Too slow update on %s, resetting", p->p.name, ifa->iface->name); } - /* On dummy, we just return non-working socket, so that user gets error every time anyone requests table */ - return rif; + + if (now >= ifa->next_regular) + { + /* Send regular update, set timer for next period (or following one if necessay) */ + TRACE(D_EVENTS, "Sending regular updates for %s", ifa->iface->name); + rip_send_table(p, ifa, ifa->addr, 0); + ifa->next_regular += period * (1 + ((now - ifa->next_regular) / period)); + ifa->want_triggered = 0; + p->triggered = 0; + } + else if (ifa->want_triggered && (now >= ifa->next_triggered)) + { + /* Send triggered update, enforce interval between triggered updates */ + TRACE(D_EVENTS, "Sending triggered updates for %s", ifa->iface->name); + rip_send_table(p, ifa, ifa->addr, ifa->want_triggered); + ifa->next_triggered = now + MIN(5, period / 2 + 1); + ifa->want_triggered = 0; + p->triggered = 0; + } + + tm_start(ifa->timer, ifa->want_triggered ? 1 : (ifa->next_regular - now)); } -static void -rip_real_if_add(struct object_lock *lock) +static inline void +rip_iface_kick_timer(struct rip_iface *ifa) { - struct iface *iface = lock->iface; - struct proto *p = lock->data; - struct rip_interface *rif; - struct iface_patt *k = iface_patt_find(&P_CF->iface_list, iface, iface->addr); - - if (!k) - bug("This can not happen! It existed few seconds ago!" ); - DBG("adding interface %s\n", iface->name ); - rif = new_iface(p, iface, iface->flags, k); - if (rif) { - add_head( &P->interfaces, NODE rif ); - DBG("Adding object lock of %p for %p\n", lock, rif); - rif->lock = lock; - } else { rfree(lock); } + if (ifa->timer->expires > (now + 1)) + tm_start(ifa->timer, 1); /* Or 100 ms */ } static void -rip_if_notify(struct proto *p, unsigned c, struct iface *iface) +rip_trigger_update(struct rip_proto *p) { - DBG( "RIP: if notify\n" ); - if (iface->flags & IF_IGNORE) + if (p->triggered) return; - if (c & IF_CHANGE_DOWN) { - struct rip_interface *i; - i = find_interface(p, iface); - if (i) { - rem_node(NODE i); - rfree(i->lock); - kill_iface(i); - } - } - if (c & IF_CHANGE_UP) { - struct iface_patt *k = iface_patt_find(&P_CF->iface_list, iface, iface->addr); - struct object_lock *lock; - struct rip_patt *PATT = (struct rip_patt *) k; - - if (!k) return; /* We are not interested in this interface */ - - lock = olock_new( p->pool ); - if (!(PATT->mode & IM_BROADCAST) && (iface->flags & IF_MULTICAST)) -#ifndef IPV6 - lock->addr = ipa_from_u32(0xe0000009); -#else - lock->addr = IP6_RIP_ROUTERS; -#endif - else - lock->addr = iface->addr->brd; - lock->port = P_CF->port; - lock->iface = iface; - lock->hook = rip_real_if_add; - lock->data = p; - lock->type = OBJLOCK_UDP; - olock_acquire(lock); + + struct rip_iface *ifa; + WALK_LIST(ifa, p->iface_list) + { + /* Interface not active */ + if (! ifa->up) + continue; + + /* Already scheduled */ + if (ifa->want_triggered) + continue; + + TRACE(D_EVENTS, "Scheduling triggered updates for %s", ifa->iface->name); + ifa->want_triggered = now; + rip_iface_kick_timer(ifa); } + + p->triggered = 1; } + +/* + * RIP protocol glue + */ + static struct ea_list * -rip_gen_attrs(struct linpool *pool, int metric, u16 tag) +rip_prepare_attrs(struct linpool *pool, ea_list *next, u8 metric, u16 tag) { - struct ea_list *l = lp_alloc(pool, sizeof(struct ea_list) + 2*sizeof(eattr)); + struct ea_list *l = lp_alloc(pool, sizeof(struct ea_list) + 2 * sizeof(eattr)); - l->next = NULL; + l->next = next; l->flags = EALF_SORTED; l->count = 2; - l->attrs[0].id = EA_RIP_TAG; + + l->attrs[0].id = EA_RIP_METRIC; l->attrs[0].flags = 0; l->attrs[0].type = EAF_TYPE_INT | EAF_TEMP; - l->attrs[0].u.data = tag; - l->attrs[1].id = EA_RIP_METRIC; + l->attrs[0].u.data = metric; + + l->attrs[1].id = EA_RIP_TAG; l->attrs[1].flags = 0; l->attrs[1].type = EAF_TYPE_INT | EAF_TEMP; - l->attrs[1].u.data = metric; + l->attrs[1].u.data = tag; + return l; } static int -rip_import_control(struct proto *p, struct rte **rt, struct ea_list **attrs, struct linpool *pool) +rip_import_control(struct proto *P, struct rte **rt, struct ea_list **attrs, struct linpool *pool) { - if ((*rt)->attrs->src->proto == p) /* My own must not be touched */ - return 1; + /* Prepare attributes with initial values */ + if ((*rt)->attrs->source != RTS_RIP) + *attrs = rip_prepare_attrs(pool, *attrs, 1, 0); - if ((*rt)->attrs->source != RTS_RIP) { - struct ea_list *new = rip_gen_attrs(pool, 1, 0); - new->next = *attrs; - *attrs = new; - } return 0; } +static int +rip_reload_routes(struct proto *P) +{ + struct rip_proto *p = (struct rip_proto *) P; + + if (p->rt_reload) + return 1; + + TRACE(D_EVENTS, "Scheduling route reload"); + p->rt_reload = 1; + rip_kick_timer(p); + + return 1; +} + static struct ea_list * rip_make_tmp_attrs(struct rte *rt, struct linpool *pool) { - return rip_gen_attrs(pool, rt->u.rip.metric, rt->u.rip.tag); + return rip_prepare_attrs(pool, NULL, rt->u.rip.metric, rt->u.rip.tag); } -static void +static void rip_store_tmp_attrs(struct rte *rt, struct ea_list *attrs) { - rt->u.rip.tag = ea_get_int(attrs, EA_RIP_TAG, 0); rt->u.rip.metric = ea_get_int(attrs, EA_RIP_METRIC, 1); + rt->u.rip.tag = ea_get_int(attrs, EA_RIP_TAG, 0); } -/* - * rip_rt_notify - core tells us about new route (possibly our - * own), so store it into our data structures. - */ -static void -rip_rt_notify(struct proto *p, struct rtable *table UNUSED, struct network *net, - struct rte *new, struct rte *old UNUSED, struct ea_list *attrs) +static int +rip_rte_better(struct rte *new, struct rte *old) { - CHK_MAGIC; - struct rip_entry *e; - - e = fib_find( &P->rtable, &net->n.prefix, net->n.pxlen ); - if (e) - fib_delete( &P->rtable, e ); - - if (new) { - e = fib_get( &P->rtable, &net->n.prefix, net->n.pxlen ); - - e->nexthop = new->attrs->gw; - e->metric = 0; - e->whotoldme = IPA_NONE; - new->u.rip.entry = e; - - e->tag = ea_get_int(attrs, EA_RIP_TAG, 0); - e->metric = ea_get_int(attrs, EA_RIP_METRIC, 1); - if (e->metric > P_CF->infinity) - e->metric = P_CF->infinity; - - if (new->attrs->src->proto == p) - e->whotoldme = new->attrs->from; - - if (!e->metric) /* That's okay: this way user can set his own value for external - routes in rip. */ - e->metric = 5; - e->updated = e->changed = now; - e->flags = 0; - } + return new->u.rip.metric < old->u.rip.metric; } static int rip_rte_same(struct rte *new, struct rte *old) { - /* new->attrs == old->attrs always */ - return new->u.rip.metric == old->u.rip.metric; + return ((new->u.rip.metric == old->u.rip.metric) && + (new->u.rip.tag == old->u.rip.tag) && + (new->u.rip.from == old->u.rip.from)); } +static struct proto * +rip_init(struct proto_config *cfg) +{ + struct proto *P = proto_new(cfg, sizeof(struct rip_proto)); + + P->accept_ra_types = RA_OPTIMAL; + P->if_notify = rip_if_notify; + P->rt_notify = rip_rt_notify; + P->neigh_notify = rip_neigh_notify; + P->import_control = rip_import_control; + P->reload_routes = rip_reload_routes; + P->make_tmp_attrs = rip_make_tmp_attrs; + P->store_tmp_attrs = rip_store_tmp_attrs; + P->rte_better = rip_rte_better; + P->rte_same = rip_rte_same; + + return P; +} + static int -rip_rte_better(struct rte *new, struct rte *old) +rip_start(struct proto *P) { - struct proto *p = new->attrs->src->proto; + struct rip_proto *p = (void *) P; + struct rip_config *cf = (void *) (P->cf); - if (ipa_equal(old->attrs->from, new->attrs->from)) - return 1; + init_list(&p->iface_list); + fib_init(&p->rtable, P->pool, sizeof(struct rip_entry), 0, rip_init_entry); + p->rte_slab = sl_new(P->pool, sizeof(struct rip_rte)); + p->timer = tm_new_set(P->pool, rip_timer, p, 0, 0); - if (old->u.rip.metric < new->u.rip.metric) - return 0; + p->ecmp = cf->ecmp; + p->infinity = cf->infinity; + p->triggered = 0; - if (old->u.rip.metric > new->u.rip.metric) - return 1; + p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 }; + p->log_rte_tbf = (struct tbf){ .rate = 4, .burst = 20 }; - if (old->attrs->src->proto == new->attrs->src->proto) /* This does not make much sense for different protocols */ - if ((old->u.rip.metric == new->u.rip.metric) && - ((now - old->lastmod) > (P_CF->timeout_time / 2))) - return 1; + tm_start(p->timer, MIN(cf->min_timeout_time, cf->max_garbage_time)); - return 0; + return PS_UP; } -/* - * rip_rte_insert - we maintain linked list of "our" entries in main - * routing table, so that we can timeout them correctly. rip_timer() - * walks the list. - */ -static void -rip_rte_insert(net *net UNUSED, rte *rte) +static int +rip_reconfigure(struct proto *P, struct proto_config *c) { - struct proto *p = rte->attrs->src->proto; - CHK_MAGIC; - DBG( "rip_rte_insert: %p\n", rte ); - add_head( &P->garbage, &rte->u.rip.garbage ); -} + struct rip_proto *p = (void *) P; + struct rip_config *new = (void *) c; + // struct rip_config *old = (void *) (P->cf); -/* - * rip_rte_remove - link list maintenance - */ -static void -rip_rte_remove(net *net UNUSED, rte *rte) -{ -#ifdef LOCAL_DEBUG - struct proto *p = rte->attrs->src->proto; - CHK_MAGIC; - DBG( "rip_rte_remove: %p\n", rte ); -#endif - rem_node( &rte->u.rip.garbage ); -} + if (new->infinity != p->infinity) + return 0; -static struct proto * -rip_init(struct proto_config *cfg) -{ - struct proto *p = proto_new(cfg, sizeof(struct rip_proto)); - - p->accept_ra_types = RA_OPTIMAL; - p->if_notify = rip_if_notify; - p->rt_notify = rip_rt_notify; - p->import_control = rip_import_control; - p->make_tmp_attrs = rip_make_tmp_attrs; - p->store_tmp_attrs = rip_store_tmp_attrs; - p->rte_better = rip_rte_better; - p->rte_same = rip_rte_same; - p->rte_insert = rip_rte_insert; - p->rte_remove = rip_rte_remove; - - return p; + TRACE(D_EVENTS, "Reconfiguring"); + + p->p.cf = c; + p->ecmp = new->ecmp; + rip_reconfigure_ifaces(p, new); + + p->rt_reload = 1; + rip_kick_timer(p); + + return 1; } -void -rip_init_config(struct rip_proto_config *c) +static void +rip_get_route_info(rte *rte, byte *buf, ea_list *attrs) { - init_list(&c->iface_list); - c->infinity = 16; - c->port = RIP_PORT; - c->period = 30; - c->garbage_time = 120+180; - c->timeout_time = 120; - c->passwords = NULL; - c->authtype = AT_NONE; + buf += bsprintf(buf, " (%d/%d)", rte->pref, rte->u.rip.metric); + + if (rte->u.rip.tag) + bsprintf(buf, " [%04x]", rte->u.rip.tag); } static int rip_get_attr(eattr *a, byte *buf, int buflen UNUSED) { - switch (a->id) { - case EA_RIP_METRIC: bsprintf( buf, "metric: %d", a->u.data ); return GA_FULL; - case EA_RIP_TAG: bsprintf( buf, "tag: %d", a->u.data ); return GA_FULL; - default: return GA_UNKNOWN; + switch (a->id) + { + case EA_RIP_METRIC: + bsprintf(buf, "metric: %d", a->u.data); + return GA_FULL; + + case EA_RIP_TAG: + bsprintf(buf, "tag: %04x", a->u.data); + return GA_FULL; + + default: + return GA_UNKNOWN; } } -static int -rip_pat_compare(struct rip_patt *a, struct rip_patt *b) +void +rip_show_interfaces(struct proto *P, char *iff) { - return ((a->metric == b->metric) && - (a->mode == b->mode) && - (a->tx_tos == b->tx_tos) && - (a->tx_priority == b->tx_priority)); + struct rip_proto *p = (void *) P; + struct rip_iface *ifa = NULL; + struct rip_neighbor *n = NULL; + + if (p->p.proto_state != PS_UP) + { + cli_msg(-1021, "%s: is not up", p->p.name); + cli_msg(0, ""); + return; + } + + cli_msg(-1021, "%s:", p->p.name); + cli_msg(-1021, "%-10s %-6s %6s %6s %6s", + "Interface", "State", "Metric", "Nbrs", "Timer"); + + WALK_LIST(ifa, p->iface_list) + { + if (iff && !patmatch(iff, ifa->iface->name)) + continue; + + int nbrs = 0; + WALK_LIST(n, ifa->neigh_list) + if (n->last_seen) + nbrs++; + + int timer = MAX(ifa->next_regular - now, 0); + cli_msg(-1021, "%-10s %-6s %6u %6u %6u", + ifa->iface->name, (ifa->up ? "Up" : "Down"), ifa->cf->metric, nbrs, timer); + } + + cli_msg(0, ""); } -static int -rip_reconfigure(struct proto *p, struct proto_config *c) +void +rip_show_neighbors(struct proto *P, char *iff) { - struct rip_proto_config *new = (struct rip_proto_config *) c; - int generic = sizeof(struct proto_config) + sizeof(list) /* + sizeof(struct password_item *) */; + struct rip_proto *p = (void *) P; + struct rip_iface *ifa = NULL; + struct rip_neighbor *n = NULL; - if (!iface_patts_equal(&P_CF->iface_list, &new->iface_list, (void *) rip_pat_compare)) - return 0; - return !memcmp(((byte *) P_CF) + generic, - ((byte *) new) + generic, - sizeof(struct rip_proto_config) - generic); + if (p->p.proto_state != PS_UP) + { + cli_msg(-1022, "%s: is not up", p->p.name); + cli_msg(0, ""); + return; + } + + cli_msg(-1022, "%s:", p->p.name); + cli_msg(-1022, "%-25s %-10s %6s %6s %6s", + "IP address", "Interface", "Metric", "Routes", "Seen"); + + WALK_LIST(ifa, p->iface_list) + { + if (iff && !patmatch(iff, ifa->iface->name)) + continue; + + WALK_LIST(n, ifa->neigh_list) + { + if (!n->last_seen) + continue; + + int timer = now - n->last_seen; + cli_msg(-1022, "%-25I %-10s %6u %6u %6u", + n->nbr->addr, ifa->iface->name, ifa->cf->metric, n->uc, timer); + } + } + + cli_msg(0, ""); } static void -rip_copy_config(struct proto_config *dest, struct proto_config *src) +rip_dump(struct proto *P) { - /* Shallow copy of everything */ - proto_copy_rest(dest, src, sizeof(struct rip_proto_config)); + struct rip_proto *p = (struct rip_proto *) P; + struct rip_iface *ifa; + int i; - /* We clean up iface_list, ifaces are non-sharable */ - init_list(&((struct rip_proto_config *) dest)->iface_list); + i = 0; + FIB_WALK(&p->rtable, e) + { + struct rip_entry *en = (struct rip_entry *) e; + debug("RIP: entry #%d: %I/%d via %I dev %s valid %d metric %d age %d s\n", + i++, en->n.prefix, en->n.pxlen, en->next_hop, en->iface->name, + en->valid, en->metric, now - en->changed); + } + FIB_WALK_END; - /* Copy of passwords is OK, it just will be replaced in dest when used */ + i = 0; + WALK_LIST(ifa, p->iface_list) + { + debug("RIP: interface #%d: %s, %I, up = %d, busy = %d\n", + i++, ifa->iface->name, ifa->sk ? ifa->sk->daddr : IPA_NONE, + ifa->up, ifa->tx_active); + } } @@ -1050,12 +1278,11 @@ struct protocol proto_rip = { .template = "rip%d", .attr_class = EAP_RIP, .preference = DEF_PREF_RIP, - .config_size = sizeof(struct rip_proto_config), + .config_size = sizeof(struct rip_config), .init = rip_init, .dump = rip_dump, .start = rip_start, .reconfigure = rip_reconfigure, - .copy_config = rip_copy_config, .get_route_info = rip_get_route_info, .get_attr = rip_get_attr }; diff --git a/proto/rip/rip.h b/proto/rip/rip.h index 2a327260..f245e612 100644 --- a/proto/rip/rip.h +++ b/proto/rip/rip.h @@ -1,185 +1,227 @@ /* - * Structures for RIP protocol + * BIRD -- Routing Information Protocol (RIP) * - FIXME: in V6, they insert additional entry whenever next hop differs. Such entry is identified by 0xff in metric. + * (c) 1998--1999 Pavel Machek <pavel@ucw.cz> + * (c) 2004--2013 Ondrej Filip <feela@network.cz> + * (c) 2009--2015 Ondrej Zajicek <santiago@crfreenet.org> + * (c) 2009--2015 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. */ +#ifndef _BIRD_RIP_H_ +#define _BIRD_RIP_H_ + +#include "nest/bird.h" +#include "nest/cli.h" +#include "nest/iface.h" +#include "nest/protocol.h" #include "nest/route.h" #include "nest/password.h" #include "nest/locks.h" +#include "nest/bfd.h" +#include "lib/lists.h" +#include "lib/resource.h" +#include "lib/socket.h" +#include "lib/string.h" +#include "lib/timer.h" -#define EA_RIP_TAG EA_CODE(EAP_RIP, 0) -#define EA_RIP_METRIC EA_CODE(EAP_RIP, 1) -#define PACKET_MAX 25 -#define PACKET_MD5_MAX 18 /* FIXME */ +#ifdef IPV6 +#define RIP_IS_V2 0 +#else +#define RIP_IS_V2 1 +#endif +#define RIP_V1 1 +#define RIP_V2 2 -#define RIP_V1 1 -#define RIP_V2 2 -#define RIP_NG 1 /* A new version numbering */ +#define RIP_PORT 520 /* RIP for IPv4 */ +#define RIP_NG_PORT 521 /* RIPng */ -#ifndef IPV6 -#define RIP_PORT 520 /* RIP for IPv4 */ -#else -#define RIP_PORT 521 /* RIPng */ -#endif +#define RIP_MAX_PKT_LENGTH 532 /* 512 + IP4_HEADER_LENGTH */ +#define RIP_AUTH_TAIL_LENGTH 20 /* 4 + MD5 length */ -struct rip_connection { - node n; +#define RIP_DEFAULT_ECMP_LIMIT 16 +#define RIP_DEFAULT_INFINITY 16 +#define RIP_DEFAULT_UPDATE_TIME 30 +#define RIP_DEFAULT_TIMEOUT_TIME 180 +#define RIP_DEFAULT_GARBAGE_TIME 120 - int num; - struct proto *proto; - ip_addr addr; - sock *send; - struct rip_interface *rif; - struct fib_iterator iter; - ip_addr daddr; - int dport; - int done; -}; +struct rip_config +{ + struct proto_config c; + list patt_list; /* List of iface configs (struct rip_iface_config) */ -struct rip_packet_heading { /* 4 bytes */ - u8 command; -#define RIPCMD_REQUEST 1 /* want info */ -#define RIPCMD_RESPONSE 2 /* responding to request */ -#define RIPCMD_TRACEON 3 /* turn tracing on */ -#define RIPCMD_TRACEOFF 4 /* turn it off */ -#define RIPCMD_MAX 5 - u8 version; -#define RIP_V1 1 -#define RIP_V2 2 -#define RIP_NG 1 /* this is verion 1 of RIPng */ - u16 unused; + u8 rip2; /* RIPv2 (IPv4) or RIPng (IPv6) */ + u8 ecmp; /* Maximum number of nexthops in ECMP route, or 0 */ + u8 infinity; /* Maximum metric value, representing infinity */ + + u32 min_timeout_time; /* Minimum of interface timeout_time */ + u32 max_garbage_time; /* Maximum of interface garbage_time */ }; -#ifndef IPV6 -struct rip_block { /* 20 bytes */ - u16 family; /* 0xffff on first message means this is authentication */ - u16 tag; - ip_addr network; - ip_addr netmask; - ip_addr nexthop; - u32 metric; +struct rip_iface_config +{ + struct iface_patt i; + ip_addr address; /* Configured dst address */ + u16 port; /* Src+dst port */ + u8 metric; /* Incoming metric */ + u8 mode; /* Interface mode (RIP_IM_*) */ + u8 passive; /* Passive iface - no packets are sent */ + u8 version; /* RIP version used for outgoing packets */ + u8 version_only; /* FIXXX */ + u8 split_horizon; /* Split horizon is used in route updates */ + u8 poison_reverse; /* Poisoned reverse is used in route updates */ + u8 check_zero; /* Validation of RIPv1 reserved fields */ + u8 ecmp_weight; /* Weight for ECMP routes*/ + u8 auth_type; /* Authentication type (RIP_AUTH_*) */ + u8 ttl_security; /* bool + 2 for TX only (send, but do not check on RX) */ + u8 check_link; /* Whether iface link change is used */ + u8 bfd; /* Use BFD on iface */ + u16 rx_buffer; /* RX buffer size, 0 for MTU */ + u16 tx_length; /* TX packet length limit (including headers), 0 for MTU */ + int tx_tos; + int tx_priority; + u32 update_time; /* Periodic update interval */ + u32 timeout_time; /* Route expiration timeout */ + u32 garbage_time; /* Unreachable entry GC timeout */ + list *passwords; /* Passwords for authentication */ }; -#else -struct rip_block { /* IPv6 version!, 20 bytes, too */ - ip_addr network; - u16 tag; - u8 pxlen; - u8 metric; + +struct rip_proto +{ + struct proto p; + struct fib rtable; /* Internal routing table */ + list iface_list; /* List of interfaces (struct rip_iface) */ + slab *rte_slab; /* Slab for internal routes (struct rip_rte) */ + timer *timer; /* Main protocol timer */ + + u8 ecmp; /* Maximum number of nexthops in ECMP route, or 0 */ + u8 infinity; /* Maximum metric value, representing infinity */ + u8 triggered; /* Logical AND of interface want_triggered values */ + u8 rt_reload; /* Route reload is scheduled */ + + struct tbf log_pkt_tbf; /* TBF for packet messages */ + struct tbf log_rte_tbf; /* TBF for RTE messages */ }; -#endif -struct rip_block_auth { /* 20 bytes */ - u16 mustbeFFFF; - u16 authtype; - u16 packetlen; - u8 keyid; - u8 authlen; - u32 seq; - u32 zero0; - u32 zero1; +struct rip_iface +{ + node n; + struct rip_proto *rip; + struct iface *iface; /* Underyling core interface */ + struct rip_iface_config *cf; /* Related config, must be updated in reconfigure */ + struct object_lock *lock; /* Interface lock */ + timer *timer; /* Interface timer */ + sock *sk; /* UDP socket */ + + u8 up; /* Interface is active */ + u8 csn_ready; /* Nonzero CSN can be used */ + u16 tx_plen; /* Max TX packet data length */ + u32 csn; /* Last used crypto sequence number */ + ip_addr addr; /* Destination multicast/broadcast address */ + list neigh_list; /* List of iface neighbors (struct rip_neighbor) */ + + /* Update scheduling */ + bird_clock_t next_regular; /* Next time when regular update should be called */ + bird_clock_t next_triggered; /* Next time when triggerd update may be called */ + bird_clock_t want_triggered; /* Nonzero if triggered update is scheduled */ + + /* Active update */ + int tx_active; /* Update session is active */ + ip_addr tx_addr; /* Update session destination address */ + bird_clock_t tx_changed; /* Minimal changed time for triggered update */ + struct fib_iterator tx_fit; /* FIB iterator in RIP routing table (p.rtable) */ }; -struct rip_md5_tail { /* 20 bytes */ - u16 mustbeFFFF; - u16 mustbe0001; - char md5[16]; +struct rip_neighbor +{ + node n; + struct rip_iface *ifa; /* Associated interface, may be NULL if stale */ + struct neighbor *nbr; /* Associaded core neighbor, may be NULL if stale */ + struct bfd_request *bfd_req; /* BFD request, if BFD is used */ + bird_clock_t last_seen; /* Time of last received and accepted message */ + u32 uc; /* Use count, number of routes linking the neighbor */ + u32 csn; /* Last received crypto sequence number */ }; -struct rip_entry { +struct rip_entry +{ struct fib_node n; + struct rip_rte *routes; /* List of incoming routes */ - ip_addr whotoldme; - ip_addr nexthop; - int metric; - u16 tag; + u8 valid; /* Entry validity state (RIP_ENTRY_*) */ + u8 metric; /* Outgoing route metric */ + u16 tag; /* Outgoing route tag */ + struct iface *from; /* Outgoing route from, NULL if from proto */ + struct iface *iface; /* Outgoing route iface (for next hop) */ + ip_addr next_hop; /* Outgoing route next hop */ - bird_clock_t updated, changed; - int flags; + bird_clock_t changed; /* Last time when the outgoing route metric changed */ }; -struct rip_packet { - struct rip_packet_heading heading; - struct rip_block block[PACKET_MAX]; -}; +struct rip_rte +{ + struct rip_rte *next; -struct rip_interface { - node n; - struct proto *proto; - struct iface *iface; - sock *sock; - struct rip_connection *busy; - int metric; /* You don't want to put struct rip_patt *patt here -- think about reconfigure */ - int mode; - int check_ttl; /* Check incoming packets for TTL 255 */ - int triggered; - struct object_lock *lock; - int multicast; + struct rip_neighbor *from; /* Advertising router */ + ip_addr next_hop; /* Route next hop (iface is from->nbr->iface) */ + u16 metric; /* Route metric (after increase) */ + u16 tag; /* Route tag */ + + bird_clock_t expires; /* Time of route expiration */ }; -struct rip_patt { - struct iface_patt i; - int metric; /* If you add entries here, don't forget to modify patt_compare! */ - int mode; -#define IM_BROADCAST 2 -#define IM_QUIET 4 -#define IM_NOLISTEN 8 -#define IM_VERSION1 16 - int tx_tos; - int tx_priority; - int ttl_security; /* bool + 2 for TX only (send, but do not check on RX) */ -}; +#define RIP_AUTH_NONE 0 +#define RIP_AUTH_PLAIN 2 +#define RIP_AUTH_CRYPTO 3 -struct rip_proto_config { - struct proto_config c; - list iface_list; /* Patterns configured -- keep it first; see rip_reconfigure why */ - list *passwords; /* Passwords, keep second */ - - int infinity; /* User configurable data; must be comparable with memcmp */ - int port; - int period; - int garbage_time; - int timeout_time; - - int authtype; -#define AT_NONE 0 -#define AT_PLAINTEXT 2 -#define AT_MD5 3 - int honor; -#define HO_NEVER 0 -#define HO_NEIGHBOR 1 -#define HO_ALWAYS 2 -}; +#define RIP_IM_MULTICAST 1 +#define RIP_IM_BROADCAST 2 -struct rip_proto { - struct proto inherited; - timer *timer; - list connections; - struct fib rtable; - list garbage; - list interfaces; /* Interfaces we really know about */ -#ifdef LOCAL_DEBUG - int magic; -#endif - int tx_count; /* Do one regular update once in a while */ - int rnd_count; /* Randomize sending time */ -}; +#define RIP_ENTRY_DUMMY 0 /* Only used to store list of incoming routes */ +#define RIP_ENTRY_VALID 1 /* Valid outgoing route */ +#define RIP_ENTRY_STALE 2 /* Stale outgoing route, waiting for GC */ -#ifdef LOCAL_DEBUG -#define RIP_MAGIC 81861253 -#define CHK_MAGIC do { if (P->magic != RIP_MAGIC) bug( "Not enough magic" ); } while (0) -#else -#define CHK_MAGIC do { } while (0) -#endif +#define EA_RIP_METRIC EA_CODE(EAP_RIP, 0) +#define EA_RIP_TAG EA_CODE(EAP_RIP, 1) +#define rip_is_v2(X) RIP_IS_V2 +#define rip_is_ng(X) (!RIP_IS_V2) -void rip_init_config(struct rip_proto_config *c); +/* +static inline int rip_is_v2(struct rip_proto *p) +{ return p->rip2; } + +static inline int rip_is_ng(struct rip_proto *p) +{ return ! p->rip2; } +*/ + +static inline void +rip_reset_tx_session(struct rip_proto *p, struct rip_iface *ifa) +{ + if (ifa->tx_active) + { + FIB_ITERATE_UNLINK(&ifa->tx_fit, &p->rtable); + ifa->tx_active = 0; + } +} + +/* rip.c */ +void rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte *new); +void rip_withdraw_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_neighbor *from); +struct rip_neighbor * rip_get_neighbor(struct rip_proto *p, ip_addr *a, struct rip_iface *ifa); +void rip_update_bfd(struct rip_proto *p, struct rip_neighbor *n); +void rip_show_interfaces(struct proto *P, char *iff); +void rip_show_neighbors(struct proto *P, char *iff); + +/* packets.c */ +void rip_send_request(struct rip_proto *p, struct rip_iface *ifa); +void rip_send_table(struct rip_proto *p, struct rip_iface *ifa, ip_addr addr, bird_clock_t changed); +int rip_open_socket(struct rip_iface *ifa); -/* Authentication functions */ -int rip_incoming_authentication( struct proto *p, struct rip_block_auth *block, struct rip_packet *packet, int num, ip_addr whotoldme ); -int rip_outgoing_authentication( struct proto *p, struct rip_block_auth *block, struct rip_packet *packet, int num ); +#endif |