#define RIP_CMD_REQUEST 1 /* want info */
#define RIP_CMD_RESPONSE 2 /* responding to request */
+#define RIP_CMD_UPDATE_REQUEST 9
+#define RIP_CMD_UPDATE_RESPONSE 10
+#define RIP_CMD_UPDATE_ACK 11
+
#define RIP_BLOCK_LENGTH 20
#define RIP_PASSWD_LENGTH 16
#define RIP_AF_IPV4 2
#define RIP_AF_AUTH 0xffff
+#define RIP_UPDATE_VERSION 1
+
/* RIP packet header */
struct rip_packet
u16 unused;
};
+/* Triggered RIP update header (RFC 2091) */
+struct rip_update_hdr
+{
+ u8 version;
+ u8 flush;
+ u16 seqnum;
+};
+
/* RTE block for RIPv2 */
struct rip_block_v2
{
ip_addr next_hop;
};
+static int
+rip_send_ack(struct rip_proto *p, struct rip_iface *ifa, uint flush, uint seqnum);
#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)
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 uint
+rip_pkt_hdrlen(struct rip_iface *ifa)
+{
+ return sizeof(struct rip_packet) +
+ (ifa->cf->demand_circuit ? sizeof(struct rip_update_hdr) : 0) +
+ (ifa->cf->auth_type ? RIP_BLOCK_LENGTH : 0);
+}
+
+static inline struct rip_update_hdr *
+rip_get_update_hdr(struct rip_packet *pkt)
+{
+ return (void *) ((byte *) pkt + sizeof(struct rip_packet));
+}
+
+static inline struct rip_block_auth *
+rip_get_auth_block(struct rip_iface *ifa, struct rip_packet *pkt)
+{
+ return (void *) ((byte *) pkt + sizeof(struct rip_packet) +
+ (ifa->cf->demand_circuit ? sizeof(struct rip_update_hdr) : 0));
+}
+
static inline void
rip_put_block(struct rip_proto *p, byte *pos, struct rip_block *rte)
}
}
+static inline void
+rip_fill_header(struct rip_iface *ifa, struct rip_packet *pkt, uint cmd)
+{
+ *pkt = (struct rip_packet) {
+ .command = cmd,
+ .version = ifa->cf->version
+ };
+}
+
+static inline void
+rip_fill_update_hdr(struct rip_packet *pkt, uint flush, uint seqnum)
+{
+ struct rip_update_hdr *hdr = rip_get_update_hdr(pkt);
+
+ *hdr = (struct rip_update_hdr) {
+ .version = RIP_UPDATE_VERSION,
+ .flush = flush,
+ .seqnum = htons(seqnum)
+ };
+}
+
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 rip_block_auth *auth = rip_get_auth_block(ifa, pkt);
struct password_item *pass = password_find(ifa->cf->passwords, 0);
if (!pass)
}
static int
-rip_check_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint *plen, struct rip_neighbor *n)
+rip_check_authentication(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint *plen, uint hdr_len, struct rip_neighbor *n)
{
- struct rip_block_auth *auth = (void *) (pkt + 1);
+ struct rip_block_auth *auth = (void *) ((byte *) pkt + hdr_len);
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))) &&
+ if ((*plen >= (hdr_len + sizeof(struct rip_block_auth))) &&
(auth->must_be_ffff == htons(0xffff)))
auth_type = ntohs(auth->auth_type);
return sk_send_to(ifa->sk, plen, dst, 0);
}
+static inline void
+rip_kick_rxmt_timer(struct rip_iface *ifa)
+{
+ if (! tm_active(ifa->rxmt_timer))
+ tm_start(ifa->rxmt_timer, ifa->cf->rxmt_time);
+}
+
void
rip_send_request(struct rip_proto *p, struct rip_iface *ifa)
{
- byte *pos = rip_tx_buffer(ifa);
+ struct rip_packet *pkt = rip_tx_buffer(ifa);
+ byte *pos = (byte *) pkt + rip_pkt_hdrlen(ifa);
+
+ rip_fill_header(ifa, pkt, RIP_CMD_REQUEST);
+
+ if (ifa->cf->demand_circuit)
+ {
+ pkt->command = RIP_CMD_UPDATE_REQUEST;
+ rip_fill_update_hdr(pkt, 0, 0);
- struct rip_packet *pkt = (void *) pos;
- pkt->command = RIP_CMD_REQUEST;
- pkt->version = ifa->cf->version;
- pkt->unused = 0;
- pos += rip_pkt_hdrlen(ifa);
+ /* Must be acknowledged by update response */
+ ifa->req_pending = 1;
+ rip_kick_rxmt_timer(ifa);
+ }
struct rip_block b = { .no_af = 1, .metric = p->infinity };
rip_put_block(p, pos, &b);
if (! ifa->tx_active)
return 0;
- byte *pos = rip_tx_buffer(ifa);
- byte *max = rip_tx_buffer(ifa) + ifa->tx_plen -
+ /* In demand circuit mode, we may wait for ACK */
+ if (ifa->tx_pending)
+ return 0;
+
+ struct rip_packet *pkt = rip_tx_buffer(ifa);
+ byte *pos = (byte *) pkt + rip_pkt_hdrlen(ifa);
+ byte *max = (byte *) pkt + ifa->tx_plen -
(rip_is_v2(p) ? RIP_BLOCK_LENGTH : 2*RIP_BLOCK_LENGTH);
ip_addr last_next_hop = IPA_NONE;
btime now_ = current_time();
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);
+ rip_fill_header(ifa, pkt, RIP_CMD_RESPONSE);
+
+ if (ifa->cf->demand_circuit)
+ {
+ pkt->command = RIP_CMD_UPDATE_RESPONSE;
+ rip_fill_update_hdr(pkt, ifa->tx_flush, ifa->tx_seqnum);
+ }
FIB_ITERATE_START(&p->rtable, &ifa->tx_fit, struct rip_entry, en)
{
if (pos > max)
{
FIB_ITERATE_PUT(&ifa->tx_fit);
- goto break_loop;
+ goto send_pkt;
}
struct rip_block rte = {
next_entry: ;
}
FIB_ITERATE_END;
- ifa->tx_active = 0;
+
+ if (send)
+ {
+ FIB_ITERATE_PUT_END(&ifa->tx_fit);
+ goto send_pkt;
+ }
/* Do not send empty packet */
- if (!send)
- return 0;
-break_loop:
+ ifa->tx_active = 0;
+
+ /* Unlink second iterator */
+ FIB_ITERATE_UNLINK(&ifa->tx_done, &p->rtable);
+
+ return 0;
+
+send_pkt:
+
+ /* Waiting for ack or timeout */
+ if (ifa->cf->demand_circuit)
+ {
+ ifa->tx_pending = 1;
+ rip_kick_rxmt_timer(ifa);
+ }
+
TRACE(D_PACKETS, "Sending response via %s", ifa->iface->name);
return rip_send_to(p, ifa, pkt, pos - (byte *) pkt, ifa->tx_addr);
}
ifa->tx_addr = addr;
ifa->tx_changed = changed;
FIB_ITERATE_INIT(&ifa->tx_fit, &p->rtable);
+ FIB_ITERATE_INIT(&ifa->tx_done, &p->rtable);
+
+ if (ifa->cf->demand_circuit)
+ ifa->tx_flush = ! changed;
rip_update_csn(p, ifa);
TRACE(D_PACKETS, "Response received from %I on %s", from->nbr->addr, ifa->iface->name);
- byte *pos = (byte *) pkt + sizeof(struct rip_packet);
+ byte *pos = (byte *) pkt + rip_pkt_hdrlen(ifa);
byte *end = (byte *) pkt + plen;
- btime now_ = current_time();
+
+ btime expires = current_time() + ifa->cf->timeout_time;
+
+ if (pkt->command == RIP_CMD_UPDATE_RESPONSE)
+ {
+ struct rip_update_hdr *hdr = rip_get_update_hdr(pkt);
+ rip_send_ack(p, ifa, hdr->flush, ntohs(hdr->seqnum));
+ expires = TIME_INFINITY;
+
+ /* Handle flush bit */
+ if (hdr->flush)
+ {
+ /* Flush old routes */
+ rip_flush_table(p, from);
+
+ /* Acknowledge pending request */
+ ifa->req_pending = 0;
+ }
+ }
for (; pos < end; pos += RIP_BLOCK_LENGTH)
{
.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
+ .expires = expires
};
rip_update_rte(p, &rte.net, &new);
}
}
+
+static int
+rip_send_ack(struct rip_proto *p, struct rip_iface *ifa, uint flush, uint seqnum)
+{
+ struct rip_packet *pkt = rip_tx_buffer(ifa);
+
+ rip_fill_header(ifa, pkt, RIP_CMD_UPDATE_ACK);
+ rip_fill_update_hdr(pkt, flush, seqnum);
+
+ TRACE(D_PACKETS, "Sending acknowledge via %s", ifa->iface->name);
+ return rip_send_to(p, ifa, pkt, rip_pkt_hdrlen(ifa), ifa->tx_addr);
+}
+
+static void
+rip_receive_ack(struct rip_proto *p, struct rip_iface *ifa, struct rip_packet *pkt, uint plen UNUSED, struct rip_neighbor *from)
+{
+ TRACE(D_PACKETS, "Acknowledge received from %I on %s", from->nbr->addr, ifa->iface->name);
+
+ struct rip_update_hdr *hdr = rip_get_update_hdr(pkt);
+ uint seqnum = ntohs(hdr->seqnum);
+
+ if (! ifa->tx_active || ! ifa->tx_pending)
+ {
+ LOG_PKT("Bad acknowledge packet from %I via %s - no pending response",
+ from->nbr->addr, ifa->iface->name);
+ return;
+ }
+
+ if (seqnum != ifa->tx_seqnum)
+ {
+ LOG_PKT("Bad acknowledge packet from %I via %s - "
+ "mismatched sequence number (rcv %u, old %u)",
+ from->nbr->addr, ifa->iface->name, seqnum, (uint) ifa->tx_seqnum);
+ return;
+ }
+
+ /* Move acked position */
+ FIB_ITERATE_COPY(&ifa->tx_done, &ifa->tx_fit, &p->rtable);
+
+ /* Packet is no longer pending */
+ ifa->tx_pending = 0;
+ ifa->tx_seqnum++;
+
+ /* Next one does not have flush bit */
+ ifa->tx_flush = 0;
+
+ rip_send_response(p, ifa);
+}
+
+/**
+ * rip_rxmt_timeout - RIP retransmission timer hook
+ * @t: timer
+ *
+ * In Demand Circuit mode, update packets must be acknowledged to ensure
+ * reliability. If they are not acknowledged, we need to retransmit them.
+ */
+void
+rip_rxmt_timeout(timer *t)
+{
+ struct rip_iface *ifa = t->data;
+ struct rip_proto *p = ifa->rip;
+
+ if (ifa->req_pending)
+ rip_send_request(p, ifa);
+
+ if (ifa->tx_pending)
+ {
+ /* Revert to acked position */
+ FIB_ITERATE_COPY(&ifa->tx_fit, &ifa->tx_done, &p->rtable);
+
+ /* Packet is no longer pending */
+ ifa->tx_pending = 0;
+ ifa->tx_seqnum++;
+
+ rip_send_response(p, ifa);
+ }
+}
+
+
static int
rip_rx_hook(sock *sk, uint len)
{
DROP("truncated", len);
struct rip_packet *pkt = (struct rip_packet *) sk->rbuf;
- uint plen = len;
+ uint pkt_len = len;
+ uint hdr_len = sizeof(struct rip_packet);
+ uint update_msg = 0;
if (!pkt->version || (ifa->cf->version_only && (pkt->version != ifa->cf->version)))
DROP("wrong version", pkt->version);
+ /* Update packets (RFC 2091) have additional header even before auth data */
+ if ((pkt->command == RIP_CMD_UPDATE_REQUEST) ||
+ (pkt->command == RIP_CMD_UPDATE_RESPONSE) ||
+ (pkt->command == RIP_CMD_UPDATE_ACK))
+ {
+ hdr_len += sizeof(struct rip_update_hdr);
+
+ if (len < hdr_len)
+ DROP("too short", len);
+
+ struct rip_update_hdr *hdr = rip_get_update_hdr(pkt);
+
+ if (hdr->version != RIP_UPDATE_VERSION)
+ DROP("wrong update header version", hdr->version);
+
+ if (hdr->flush > 1)
+ DROP("wrong flush value", hdr->flush);
+
+ update_msg = 1;
+ }
+
/* rip_check_authentication() has its own error logging */
- if (rip_is_v2(p) && !rip_check_authentication(p, ifa, pkt, &plen, n))
+ if (rip_is_v2(p) && !rip_check_authentication(p, ifa, pkt, &pkt_len, hdr_len, n))
return 1;
- if ((plen - sizeof(struct rip_packet)) % RIP_BLOCK_LENGTH)
- DROP("invalid length", plen);
+ if ((pkt_len - hdr_len) % RIP_BLOCK_LENGTH)
+ DROP("invalid length", pkt_len);
+
+ if (update_msg != ifa->cf->demand_circuit)
+ DROP("demand circuit mode mismatch", update_msg);
n->last_seen = current_time();
rip_update_bfd(p, n);
switch (pkt->command)
{
case RIP_CMD_REQUEST:
- rip_receive_request(p, ifa, pkt, plen, n);
+ case RIP_CMD_UPDATE_REQUEST:
+ rip_receive_request(p, ifa, pkt, pkt_len, n);
break;
case RIP_CMD_RESPONSE:
- rip_receive_response(p, ifa, pkt, plen, n);
+ case RIP_CMD_UPDATE_RESPONSE:
+ rip_receive_response(p, ifa, pkt, pkt_len, n);
+ break;
+
+ case RIP_CMD_UPDATE_ACK:
+ rip_receive_ack(p, ifa, pkt, pkt_len, n);
break;
default:
}
}
+void
+rip_flush_table(struct rip_proto *p, struct rip_neighbor *n)
+{
+ btime expires = current_time() + n->ifa->cf->timeout_time;
+
+ FIB_WALK(&p->rtable, struct rip_entry, en)
+ {
+ for (struct rip_rte *e = en->routes; e; e = e->next)
+ if ((e->from == n) && (e->expires == TIME_INFINITY))
+ e->expires = expires;
+ }
+ FIB_WALK_END;
+}
+
/*
* RIP neighbors
TRACE(D_EVENTS, "Starting interface %s", ifa->iface->name);
- ifa->next_regular = current_time() + (random() % ifa->cf->update_time) + 100 MS;
- ifa->next_triggered = current_time(); /* Available immediately */
- ifa->want_triggered = 1; /* All routes in triggered update */
- tm_start(ifa->timer, 100 MS);
+ if (! ifa->cf->demand_circuit)
+ {
+ ifa->next_regular = current_time() + (random() % ifa->cf->update_time) + 100 MS;
+ tm_set(ifa->timer, ifa->next_regular);
+ }
+ else
+ {
+ ifa->next_regular = TIME_INFINITY;
+ }
+
ifa->up = 1;
- if (!ifa->cf->passive)
- rip_send_request(ifa->rip, ifa);
+ rip_send_request(p, ifa);
+ rip_send_table(p, ifa, ifa->addr, 0);
}
static void
rip_reset_tx_session(p, ifa);
+ ifa->next_regular = 0;
+ ifa->next_triggered = 0;
+ ifa->want_triggered = 0;
+
+ ifa->tx_pending = 0;
+ ifa->req_pending = 0;
+
WALK_LIST_FIRST(n, ifa->neigh_list)
rip_remove_neighbor(p, n);
tm_stop(ifa->timer);
+ tm_stop(ifa->rxmt_timer);
ifa->up = 0;
}
add_tail(&p->iface_list, NODE ifa);
ifa->timer = tm_new_init(p->p.pool, rip_iface_timer, ifa, 0, 0);
+ ifa->rxmt_timer = tm_new_init(p->p.pool, rip_rxmt_timeout, ifa, 0, 0);
struct object_lock *lock = olock_new(p->p.pool);
lock->type = OBJLOCK_UDP;
/* Handling neighbor expiration */
WALK_LIST(ifa, p->iface_list)
+ {
+ /* No expiration for demand circuit ifaces */
+ if (ifa->cf->demand_circuit)
+ continue;
+
WALK_LIST_DELSAFE(n, nn, ifa->neigh_list)
if (n->last_seen)
{
else
next = MIN(next, expires);
}
+ }
tm_start(p->timer, MAX(next - now_, 100 MS));
}
static inline void
rip_kick_timer(struct rip_proto *p)
{
- if (p->timer->expires > (current_time() + 100 MS))
+ if ((p->timer->expires > (current_time() + 100 MS)))
tm_start(p->timer, 100 MS);
}
if (ifa->tx_active)
{
- if (now_ < (ifa->next_regular + period))
- { tm_start(ifa->timer, 100 MS); 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);
+ tm_start(ifa->timer, 100 MS);
+ return;
}
if (now_ >= ifa->next_regular)
p->triggered = 0;
}
- tm_start(ifa->timer, ifa->want_triggered ? (1 S) : (ifa->next_regular - now_));
+ if (ifa->want_triggered && (ifa->next_triggered < ifa->next_regular))
+ tm_set(ifa->timer, ifa->next_triggered);
+ else if (ifa->next_regular != TIME_INFINITY)
+ tm_set(ifa->timer, ifa->next_regular);
}
+
static inline void
rip_iface_kick_timer(struct rip_iface *ifa)
{
- if (ifa->timer->expires > (current_time() + 100 MS))
+ if ((! tm_active(ifa->timer)) || (ifa->timer->expires > (current_time() + 100 MS)))
tm_start(ifa->timer, 100 MS);
}
TRACE(D_EVENTS, "Scheduling triggered updates for %s", ifa->iface->name);
ifa->want_triggered = current_time();
rip_iface_kick_timer(ifa);
+ p->triggered = 1;
}
-
- p->triggered = 1;
}
nbrs++;
btime now_ = current_time();
- btime timer = (ifa->next_regular > now_) ? (ifa->next_regular - now_) : 0;
+ btime timer = ((ifa->next_regular < TIME_INFINITY) && (ifa->next_regular > now_)) ?
+ (ifa->next_regular - now_) : 0;
cli_msg(-1021, "%-10s %-6s %6u %6u %7t",
ifa->iface->name, (ifa->up ? "Up" : "Down"), ifa->cf->metric, nbrs, timer);
}
#define RIP_DEFAULT_UPDATE_TIME (30 S_)
#define RIP_DEFAULT_TIMEOUT_TIME (180 S_)
#define RIP_DEFAULT_GARBAGE_TIME (120 S_)
+#define RIP_DEFAULT_RXMT_TIME (1 S_)
struct rip_config
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 demand_circuit; /* Use demand circuit extensions (RFC 2091) */
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 */
btime update_time; /* Periodic update interval */
btime timeout_time; /* Route expiration timeout */
btime garbage_time; /* Unreachable entry GC timeout */
+ btime rxmt_time; /* Retransmit timeout for demand circuit */
list *passwords; /* Passwords for authentication */
};
struct rip_iface_config *cf; /* Related config, must be updated in reconfigure */
struct object_lock *lock; /* Interface lock */
timer *timer; /* Interface timer */
+ timer *rxmt_timer; /* Retransmission timer */
sock *sk; /* UDP socket */
u8 up; /* Interface is active */
/* Active update */
int tx_active; /* Update session is active */
+ int tx_waiting;
ip_addr tx_addr; /* Update session destination address */
btime tx_changed; /* Minimal changed time for triggered update */
struct fib_iterator tx_fit; /* FIB iterator in RIP routing table (p.rtable) */
+ struct fib_iterator tx_done; /* FIB iterator for acked routes (p.rtable) */
+
+ /* Update message */
+ u8 tx_pending;
+ u8 tx_flush;
+ u16 tx_seqnum;
+
+ u8 req_pending;
};
struct rip_neighbor
if (ifa->tx_active)
{
FIB_ITERATE_UNLINK(&ifa->tx_fit, &p->rtable);
+ FIB_ITERATE_UNLINK(&ifa->tx_done, &p->rtable);
ifa->tx_active = 0;
}
}
/* rip.c */
void rip_update_rte(struct rip_proto *p, net_addr *n, struct rip_rte *new);
void rip_withdraw_rte(struct rip_proto *p, net_addr *n, struct rip_neighbor *from);
+void rip_flush_table(struct rip_proto *p, struct rip_neighbor *n);
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);
/* 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, btime changed);
+void rip_rxmt_timeout(timer *t);
int rip_open_socket(struct rip_iface *ifa);