bird/nest/proto.c
Martin Mares 96d8e3bff2 Added protocol debugging flags (protocol.h: D_xxx), parsing of them
in configuration files and commands for manipulating them.

Current debug message policy:

   o  D_STATES, D_ROUTES and D_FILTERS are handled in generic code.
   o  Other debug flags should be handled in the protocols and whenever
      the flag is set, the corresponding messages should be printed
      using calls to log(L_TRACE, ...), each message prefixed with
      the name of the protocol instance. These messages should cover
      the whole normal operation of the protocol and should be useful
      for an administrator trying to understand what does the protocol
      behave on his network or who is attempting to diagnose network
      problems. If your messages don't fit to the categories I've defined,
      feel free to add your own ones (by adding them to protocol.h
      and on two places in nest/config.Y), but please try to keep the
      categories as general as possible (i.e., not tied to your protocol).
   o  Internal debug messages not interesting even to an experienced
      user should be printed by calling DBG() which is either void or
      a call to debug() depending on setting of the LOCAL_DEBUG symbol
      at the top of your source.
   o  Dump functions (proto->dump etc.) should call debug() to print
      their messages.
   o  If you are doing any internal consistency checks, use ASSERT
      or bug().
   o  Nobody shall ever call printf() or any other stdio functions.

Also please try to log any protocol errors you encounter and tag them
with the appropriate message category (usually L_REMOTE or L_AUTH). Always
carefully check contents of any message field you receive and verify all
IP addresses you work with (by calling ipa_classify() or by using the
neighbour cache if you want to check direct connectedness as well).
2000-03-07 20:49:48 +00:00

621 lines
14 KiB
C

/*
* BIRD -- Protocols
*
* (c) 1998--2000 Martin Mares <mj@ucw.cz>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#define LOCAL_DEBUG
#include <string.h>
#include "nest/bird.h"
#include "nest/protocol.h"
#include "lib/resource.h"
#include "lib/lists.h"
#include "lib/event.h"
#include "lib/string.h"
#include "conf/conf.h"
#include "nest/route.h"
#include "nest/iface.h"
#include "nest/cli.h"
#include "filter/filter.h"
static pool *proto_pool;
list protocol_list;
static list proto_list;
#define WALK_PROTO_LIST(p) do { \
node *nn; \
WALK_LIST(nn, proto_list) { \
struct proto *p = SKIP_BACK(struct proto, glob_node, nn);
#define WALK_PROTO_LIST_END } } while(0)
list active_proto_list;
static list inactive_proto_list;
static list initial_proto_list;
static list flush_proto_list;
static event *proto_flush_event;
static char *p_states[] = { "DOWN", "START", "UP", "STOP" };
static char *c_states[] = { "HUNGRY", "FEEDING", "HAPPY", "FLUSHING" };
static int proto_flush_all(void *);
static void proto_rethink_goal(struct proto *p);
static void
proto_enqueue(list *l, struct proto *p)
{
add_tail(l, &p->n);
p->last_state_change = now;
}
static void
proto_relink(struct proto *p)
{
list *l;
rem_node(&p->n);
switch (p->core_state)
{
case FS_HAPPY:
l = &active_proto_list;
break;
case FS_FLUSHING:
l = &flush_proto_list;
break;
default:
l = &inactive_proto_list;
}
proto_enqueue(l, p);
}
void *
proto_new(struct proto_config *c, unsigned size)
{
struct protocol *pr = c->protocol;
struct proto *p = mb_allocz(proto_pool, size);
p->cf = c;
p->debug = c->debug;
p->name = c->name;
p->preference = c->preference;
p->disabled = c->disabled;
p->proto = pr;
p->table = c->table->table;
p->in_filter = c->in_filter;
p->out_filter = c->out_filter;
p->min_scope = SCOPE_SITE;
p->hash_key = random_u32();
c->proto = p;
return p;
}
static void
proto_init_instance(struct proto *p)
{
/* Here we cannot use p->cf->name since it won't survive reconfiguration */
p->pool = rp_new(proto_pool, p->proto->name);
p->attn = ev_new(p->pool);
p->attn->data = p;
rt_lock_table(p->table);
}
struct announce_hook *
proto_add_announce_hook(struct proto *p, struct rtable *t)
{
struct announce_hook *h;
if (!p->rt_notify)
return NULL;
DBG("Connecting protocol %s to table %s\n", p->name, t->name);
h = mb_alloc(p->pool, sizeof(struct announce_hook));
h->table = t;
h->proto = p;
h->next = p->ahooks;
p->ahooks = h;
add_tail(&t->hooks, &h->n);
return h;
}
static void
proto_flush_hooks(struct proto *p)
{
struct announce_hook *h;
for(h=p->ahooks; h; h=h->next)
rem_node(&h->n);
p->ahooks = NULL;
}
void *
proto_config_new(struct protocol *pr, unsigned size)
{
struct proto_config *c = cfg_allocz(size);
add_tail(&new_config->protos, &c->n);
c->global = new_config;
c->protocol = pr;
c->name = pr->name;
c->out_filter = FILTER_REJECT;
c->table = c->global->master_rtc;
return c;
}
void
protos_preconfig(struct config *c)
{
struct protocol *p;
init_list(&c->protos);
debug("Protocol preconfig:");
WALK_LIST(p, protocol_list)
{
debug(" %s", p->name);
p->name_counter = 0;
if (p->preconfig)
p->preconfig(p, c);
}
debug("\n");
}
void
protos_postconfig(struct config *c)
{
struct proto_config *x;
struct protocol *p;
debug("Protocol postconfig:");
WALK_LIST(x, c->protos)
{
debug(" %s", x->name);
p = x->protocol;
if (p->postconfig)
p->postconfig(x);
}
debug("\n");
}
static struct proto *
proto_init(struct proto_config *c)
{
struct protocol *p = c->protocol;
struct proto *q = p->init(c);
q->proto_state = PS_DOWN;
q->core_state = FS_HUNGRY;
proto_enqueue(&initial_proto_list, q);
add_tail(&proto_list, &q->glob_node);
return q;
}
void
protos_commit(struct config *new, struct config *old, int force_reconfig)
{
struct proto_config *oc, *nc;
struct proto *p, *n;
DBG("protos_commit:\n");
if (old)
{
WALK_LIST(oc, old->protos)
{
struct proto *p = oc->proto;
struct symbol *sym = cf_find_symbol(oc->name);
if (sym && sym->class == SYM_PROTO && !new->shutdown)
{
/* Found match, let's check if we can smoothly switch to new configuration */
nc = sym->def;
if (!force_reconfig
&& nc->protocol == oc->protocol
&& nc->preference == oc->preference
&& nc->disabled == oc->disabled
&& nc->table->table == oc->table->table
&& filter_same(nc->in_filter, oc->in_filter)
&& filter_same(nc->out_filter, oc->out_filter)
&& p->proto_state != PS_DOWN)
{
/* Generic attributes match, try converting them and then ask the protocol */
p->debug = nc->debug;
if (p->proto->reconfigure && p->proto->reconfigure(p, nc))
{
DBG("\t%s: same\n", oc->name);
p->cf = nc;
p->name = nc->name;
nc->proto = p;
continue;
}
}
/* Unsuccessful, force reconfig */
DBG("\t%s: power cycling\n", oc->name);
p->cf_new = nc;
nc->proto = p;
}
else
{
DBG("\t%s: deleting\n", oc->name);
p->cf_new = NULL;
}
p->reconfiguring = 1;
config_add_obstacle(old);
proto_rethink_goal(p);
}
}
WALK_LIST(nc, new->protos)
if (!nc->proto)
{
DBG("\t%s: adding\n", nc->name);
proto_init(nc);
}
DBG("\tdone\n");
DBG("Protocol start\n");
WALK_LIST_DELSAFE(p, n, initial_proto_list)
proto_rethink_goal(p);
}
static void
proto_rethink_goal(struct proto *p)
{
struct protocol *q;
if (p->reconfiguring && p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN)
{
struct proto_config *nc = p->cf_new;
DBG("%s has shut down for reconfiguration\n", p->name);
config_del_obstacle(p->cf->global);
rem_node(&p->n);
rem_node(&p->glob_node);
mb_free(p);
if (!nc)
return;
p = proto_init(nc); /* FIXME: What about protocol priorities??? */
}
/* Determine what state we want to reach */
if (p->disabled || p->reconfiguring)
p->core_goal = FS_HUNGRY;
else
p->core_goal = FS_HAPPY;
if (p->core_state == p->core_goal)
return;
q = p->proto;
if (p->core_goal == FS_HAPPY) /* Going up */
{
if (p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN)
{
DBG("Kicking %s up\n", p->name);
proto_init_instance(p);
proto_notify_state(p, (q->start ? q->start(p) : PS_UP));
}
}
else /* Going down */
{
if (p->proto_state == PS_START || p->proto_state == PS_UP)
{
DBG("Kicking %s down\n", p->name);
proto_notify_state(p, (q->shutdown ? q->shutdown(p) : PS_DOWN));
}
}
}
void
protos_dump_all(void)
{
struct proto *p;
debug("Protocols:\n");
WALK_LIST(p, active_proto_list)
{
debug(" protocol %s state %s/%s\n", p->name,
p_states[p->proto_state], c_states[p->core_state]);
if (p->in_filter)
debug("\tInput filter: %s\n", filter_name(p->in_filter));
if (p->out_filter != FILTER_REJECT)
debug("\tOutput filter: %s\n", filter_name(p->out_filter));
if (p->disabled)
debug("\tDISABLED\n");
else if (p->proto->dump)
p->proto->dump(p);
}
WALK_LIST(p, inactive_proto_list)
debug(" inactive %s: state %s/%s\n", p->name, p_states[p->proto_state], c_states[p->core_state]);
WALK_LIST(p, initial_proto_list)
debug(" initial %s\n", p->name);
WALK_LIST(p, flush_proto_list)
debug(" flushing %s\n", p->name);
}
void
protos_build(void)
{
init_list(&protocol_list);
init_list(&proto_list);
init_list(&active_proto_list);
init_list(&inactive_proto_list);
init_list(&initial_proto_list);
init_list(&flush_proto_list);
add_tail(&protocol_list, &proto_device.n);
#ifdef CONFIG_RIP
add_tail(&protocol_list, &proto_rip.n);
#endif
#ifdef CONFIG_STATIC
add_tail(&protocol_list, &proto_static.n);
#endif
#ifdef CONFIG_OSPF
add_tail(&protocol_list, &proto_ospf.n);
#endif
#ifdef CONFIG_PIPE
add_tail(&protocol_list, &proto_pipe.n);
#endif
proto_pool = rp_new(&root_pool, "Protocols");
proto_flush_event = ev_new(proto_pool);
proto_flush_event->hook = proto_flush_all;
}
static void
proto_fell_down(struct proto *p)
{
DBG("Protocol %s down\n", p->name);
rt_unlock_table(p->table);
proto_rethink_goal(p);
}
static int
proto_feed(void *P)
{
struct proto *p = P;
DBG("Feeding protocol %s\n", p->name);
proto_add_announce_hook(p, p->table);
if_feed_baby(p);
rt_feed_baby(p);
p->core_state = FS_HAPPY;
proto_relink(p);
DBG("Protocol %s up and running\n", p->name);
return 0;
}
void
proto_notify_state(struct proto *p, unsigned ps)
{
unsigned ops = p->proto_state;
unsigned cs = p->core_state;
DBG("%s reporting state transition %s/%s -> */%s\n", p->name, c_states[cs], p_states[ops], p_states[ps]);
if (ops == ps)
return;
switch (ps)
{
case PS_DOWN:
if (cs == FS_HUNGRY) /* Shutdown finished */
{
proto_fell_down(p);
return; /* The protocol might have ceased to exist */
}
else if (cs == FS_FLUSHING) /* Still flushing... */
;
else /* Need to start flushing */
goto schedule_flush;
break;
case PS_START:
ASSERT(ops == PS_DOWN);
ASSERT(cs == FS_HUNGRY);
break;
case PS_UP:
ASSERT(ops == PS_DOWN || ops == PS_START);
ASSERT(cs == FS_HUNGRY);
DBG("%s: Scheduling meal\n", p->name);
cs = FS_FEEDING;
p->attn->hook = proto_feed;
ev_schedule(p->attn);
break;
case PS_STOP:
if (cs == FS_FEEDING || cs == FS_HAPPY)
{
schedule_flush:
DBG("%s: Scheduling flush\n", p->name);
proto_flush_hooks(p);
cs = FS_FLUSHING;
ev_schedule(proto_flush_event);
}
break;
default:
error:
bug("Invalid state transition for %s from %s/%s to */%s", p->name, c_states[cs], p_states[ops], p_states[ps]);
}
p->proto_state = ps;
p->core_state = cs;
proto_relink(p);
}
static int
proto_flush_all(void *unused)
{
struct proto *p;
rt_prune_all();
neigh_prune();
while ((p = HEAD(flush_proto_list))->n.next)
{
DBG("Flushing protocol %s\n", p->name);
rfree(p->pool);
p->pool = NULL;
p->core_state = FS_HUNGRY;
proto_relink(p);
proto_fell_down(p);
}
return 0;
}
/*
* CLI Commands
*/
static char *
proto_state_name(struct proto *p)
{
#define P(x,y) ((x << 4) | y)
switch (P(p->proto_state, p->core_state))
{
case P(PS_DOWN, FS_HUNGRY): return "down";
case P(PS_START, FS_HUNGRY): return "start";
case P(PS_UP, FS_HUNGRY):
case P(PS_UP, FS_FEEDING): return "feed";
case P(PS_STOP, FS_HUNGRY): return "stop";
case P(PS_UP, FS_HAPPY): return "up";
case P(PS_STOP, FS_FLUSHING):
case P(PS_DOWN, FS_FLUSHING): return "flush";
default: return "???";
}
#undef P
}
static void
proto_do_show(struct proto *p, int verbose)
{
byte buf[256], reltime[TM_RELTIME_BUFFER_SIZE];
buf[0] = 0;
if (p->proto->get_status)
p->proto->get_status(p, buf);
tm_format_reltime(reltime, p->last_state_change);
cli_msg(-1002, "%-8s %-8s %-8s %-5s %-5s %s",
p->name,
p->proto->name,
p->table->name,
proto_state_name(p),
reltime,
buf);
if (verbose)
{
cli_msg(-1006, "\tPreference: %d", p->preference);
cli_msg(-1006, "\tInput filter: %s", filter_name(p->in_filter));
cli_msg(-1006, "\tOutput filter: %s", filter_name(p->out_filter));
}
}
void
proto_show(struct symbol *s, int verbose)
{
if (s && s->class != SYM_PROTO)
{
cli_msg(9002, "%s is not a protocol", s->name);
return;
}
cli_msg(-2002, "name proto table state since info");
if (s)
proto_do_show(((struct proto_config *)s->def)->proto, verbose);
else
{
WALK_PROTO_LIST(p)
proto_do_show(p, verbose);
WALK_PROTO_LIST_END;
}
cli_msg(0, "");
}
struct proto *
proto_get_named(struct symbol *sym, struct protocol *pr)
{
struct proto *p, *q;
if (sym)
{
if (sym->class != SYM_PROTO)
cf_error("%s: Not a protocol", sym->name);
p = ((struct proto_config *)sym->def)->proto;
if (!p || p->proto != pr)
cf_error("%s: Not a %s protocol", sym->name, pr->name);
}
else
{
p = NULL;
WALK_LIST(q, active_proto_list)
if (q->proto == pr)
{
if (p)
cf_error("There are multiple %s protocols running", pr->name);
p = q;
}
if (!p)
cf_error("There is no %s protocol running", pr->name);
}
return p;
}
void
proto_xxable(char *pattern, int xx)
{
int cnt = 0;
WALK_PROTO_LIST(p)
if (patmatch(pattern, p->name))
{
cnt++;
switch (xx)
{
case 0:
if (p->disabled)
cli_msg(-8, "%s: already disabled", p->name);
else
{
cli_msg(-9, "%s: disabled", p->name);
p->disabled = 1;
}
break;
case 1:
if (!p->disabled)
cli_msg(-10, "%s: already enabled", p->name);
else
{
cli_msg(-11, "%s: enabled", p->name);
p->disabled = 0;
}
break;
case 2:
if (p->disabled)
cli_msg(-8, "%s: already disabled", p->name);
else
{
p->disabled = 1;
proto_rethink_goal(p);
p->disabled = 0;
cli_msg(-12, "%s: restarted", p->name);
}
break;
default:
ASSERT(0);
}
proto_rethink_goal(p);
}
WALK_PROTO_LIST_END;
if (!cnt)
cli_msg(8003, "No protocols match");
else
cli_msg(0, "");
}
void
proto_debug(char *pattern, unsigned int mask)
{
int cnt = 0;
WALK_PROTO_LIST(p)
if (patmatch(pattern, p->name))
{
cnt++;
p->debug = mask;
}
WALK_PROTO_LIST_END;
if (!cnt)
cli_msg(8003, "No protocols match");
else
cli_msg(0, "");
}