diff --git a/README b/README index c363d7a5..2e7e4dcb 100644 --- a/README +++ b/README @@ -66,6 +66,7 @@ What do we support: o OSPF o Static routes o Inter-table protocol + o IPv6 router advertisements o Command-line interface (using the `birdc' client; to get some help, just press `?') o Soft reconfiguration -- no online commands for changing the diff --git a/TODO b/TODO index 9b770c5b..55db0b82 100644 --- a/TODO +++ b/TODO @@ -29,7 +29,6 @@ Various ideas - filters: user defined attributes? - client: access control - io: use poll if available -- IPv6 router advertisements - real multipath (doesn't seem to be simple at all :() - fake multipath (even less simple) - route recalculation timing and flap dampening [see RFC2439 for algorithms] diff --git a/conf/cf-lex.l b/conf/cf-lex.l index a99acdc8..c6e9a0ea 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -31,6 +31,8 @@ #include #include +#define PARSER 1 + #include "nest/bird.h" #include "nest/route.h" #include "nest/protocol.h" diff --git a/conf/confbase.Y b/conf/confbase.Y index 534b332f..ce844ba5 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -8,6 +8,8 @@ CF_HDR +#define PARSER 1 + #include "nest/bird.h" #include "conf/conf.h" #include "lib/resource.h" diff --git a/configure.in b/configure.in index 4d023324..6b067900 100644 --- a/configure.in +++ b/configure.in @@ -42,13 +42,15 @@ AC_SUBST(srcdir_rel_mf) if test "$enable_ipv6" = yes ; then ip=ipv6 SUFFIX6=6 + all_protocols=bgp,ospf,pipe,radv,rip,static else ip=ipv4 SUFFIX6="" + all_protocols=bgp,ospf,pipe,rip,static fi if test "$with_protocols" = all ; then - with_protocols=bgp,ospf,pipe,rip,static + with_protocols="$all_protocols" fi AC_SEARCH_LIBS(clock_gettime,[c rt posix4]) diff --git a/lib/birdlib.h b/lib/birdlib.h index c377a646..479f3d5c 100644 --- a/lib/birdlib.h +++ b/lib/birdlib.h @@ -19,8 +19,14 @@ /* Utility macros */ +#ifdef PARSER +#define _MIN(a,b) (((a)<(b))?(a):(b)) +#define _MAX(a,b) (((a)>(b))?(a):(b)) +#else #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + #define ABS(a) ((a)>=0 ? (a) : -(a)) #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a))) diff --git a/lib/socket.h b/lib/socket.h index e6ce1257..348295f5 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -67,6 +67,7 @@ int sk_leave_group(sock *s, ip_addr maddr); #ifdef IPV6 int sk_set_ipv6_checksum(sock *s, int offset); +int sk_set_icmp_filter(sock *s, int p1, int p2); #endif int sk_set_broadcast(sock *s, int enable); diff --git a/nest/proto-hooks.c b/nest/proto-hooks.c index 3d19e3fe..f0261922 100644 --- a/nest/proto-hooks.c +++ b/nest/proto-hooks.c @@ -261,7 +261,7 @@ void store_tmp_attrs(rte *e, ea_list *attrs) * The standard use of this hook is to reject routes having originated * from the same instance and to set default values of the protocol's metrics. * - * Result: -1 if the route has to be accepted, 1 if rejected and 0 if it + * Result: 1 if the route has to be accepted, -1 if rejected and 0 if it * should be passed to the filters. */ int import_control(struct proto *p, rte **e, ea_list **attrs, struct linpool *pool) diff --git a/nest/proto.c b/nest/proto.c index 16ec3f9b..d6d5d77b 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -546,6 +546,9 @@ protos_build(void) init_list(&initial_proto_list); init_list(&flush_proto_list); proto_build(&proto_device); +#ifdef CONFIG_RADV + proto_build(&proto_radv); +#endif #ifdef CONFIG_RIP proto_build(&proto_rip); #endif diff --git a/nest/protocol.h b/nest/protocol.h index 70999f0e..f95905ae 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -71,7 +71,7 @@ void protos_dump_all(void); */ extern struct protocol - proto_device, proto_rip, proto_static, + proto_device, proto_radv, proto_rip, proto_static, proto_ospf, proto_pipe, proto_bgp; /* diff --git a/proto/Doc b/proto/Doc index 18e193a2..16b084fb 100644 --- a/proto/Doc +++ b/proto/Doc @@ -3,5 +3,6 @@ C bgp C ospf C pipe C rip +C radv C static S ../nest/rt-dev.c diff --git a/proto/radv/Doc b/proto/radv/Doc new file mode 100644 index 00000000..d051692a --- /dev/null +++ b/proto/radv/Doc @@ -0,0 +1,2 @@ +S radv.c +S packets.c diff --git a/proto/radv/Makefile b/proto/radv/Makefile new file mode 100644 index 00000000..efc4d4af --- /dev/null +++ b/proto/radv/Makefile @@ -0,0 +1,5 @@ +source=radv.c packets.c +root-rel=../../ +dir-name=proto/radv + +include ../../Rules diff --git a/proto/radv/config.Y b/proto/radv/config.Y new file mode 100644 index 00000000..78a09551 --- /dev/null +++ b/proto/radv/config.Y @@ -0,0 +1,157 @@ +/* + * BIRD -- Router Advertisement Configuration + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#include "proto/radv/radv.h" + +CF_DEFINES + +#define RADV_CFG ((struct radv_config *) this_proto) +#define RADV_IFACE ((struct radv_iface_config *) this_ipatt) +#define RADV_PREFIX this_radv_prefix + +static struct radv_prefix_config *this_radv_prefix; + +CF_DECLS + +CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL, + MANAGED, OTHER, CONFIG, LINK, MTU, REACHABLE, TIME, RETRANS, + TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, + LIFETIME, SKIP, ONLINK, AUTONOMOUS) + + +CF_GRAMMAR + +CF_ADDTO(proto, radv_proto '}') + +radv_proto_start: proto_start RADV +{ + this_proto = proto_config_new(&proto_radv, sizeof(struct radv_config)); + init_list(&RADV_CFG->patt_list); + init_list(&RADV_CFG->pref_list); +}; + +radv_proto_item: + proto_item + | PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); } + | INTERFACE radv_iface + ; + +radv_proto_opts: + /* empty */ + | radv_proto_opts radv_proto_item ';' + ; + +radv_proto: + radv_proto_start proto_name '{' radv_proto_opts; + + +radv_iface_start: +{ + this_ipatt = cfg_allocz(sizeof(struct radv_iface_config)); + add_tail(&RADV_CFG->patt_list, NODE this_ipatt); + init_list(&this_ipatt->ipn_list); + init_list(&RADV_IFACE->pref_list); + + RADV_IFACE->min_ra_int = -1; /* undefined */ + RADV_IFACE->max_ra_int = DEFAULT_MAX_RA_INT; + RADV_IFACE->min_delay = DEFAULT_MIN_DELAY; + RADV_IFACE->current_hop_limit = DEFAULT_CURRENT_HOP_LIMIT; + RADV_IFACE->default_lifetime = -1; +}; + +radv_iface_item: + MIN RA INTERVAL expr { RADV_IFACE->min_ra_int = $4; if ($4 < 3) cf_error("Min RA interval must be at least 3"); } + | MAX RA INTERVAL expr { RADV_IFACE->max_ra_int = $4; if (($4 < 4) || ($4 > 1800)) cf_error("Max RA interval must be in range 4-1800"); } + | MIN DELAY expr { RADV_IFACE->min_delay = $3; if ($3 <= 0) cf_error("Min delay must be positive"); } + | MANAGED bool { RADV_IFACE->managed = $2; } + | OTHER CONFIG bool { RADV_IFACE->other_config = $3; } + | LINK MTU expr { RADV_IFACE->link_mtu = $3; if ($3 < 0) cf_error("Link MTU must be 0 or positive"); } + | REACHABLE TIME expr { RADV_IFACE->reachable_time = $3; if (($3 < 0) || ($3 > 3600000)) cf_error("Reachable time must be in range 0-3600000"); } + | RETRANS TIMER expr { RADV_IFACE->retrans_timer = $3; if ($3 < 0) cf_error("Retrans timer must be 0 or positive"); } + | CURRENT HOP LIMIT expr { RADV_IFACE->current_hop_limit = $4; if (($4 < 0) || ($4 > 255)) cf_error("Current hop limit must be in range 0-255"); } + | DEFAULT LIFETIME expr { RADV_IFACE->default_lifetime = $3; if (($3 < 0) || ($3 > 9000)) cf_error("Default lifetime must be in range 0-9000"); } + | PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); } + ; + +radv_iface_finish: +{ + struct radv_iface_config *ic = RADV_IFACE; + + if (ic->min_ra_int == (u32) -1) + ic->min_ra_int = _MAX(ic->max_ra_int / 3, 3); + + if (ic->default_lifetime == (u32) -1) + ic->default_lifetime = 3 * ic->max_ra_int; + + if ((ic->min_ra_int > 3) && + (ic->min_ra_int > (ic->max_ra_int * 3 / 4))) + cf_error("Min RA interval must be at most 3/4 * Max RA interval %d %d", ic->min_ra_int, ic->max_ra_int); + + if ((ic->default_lifetime > 0) && (ic->default_lifetime < ic->max_ra_int)) + cf_error("Default lifetime must be either 0 or at least Max RA interval"); +}; + + +radv_iface_opts: + /* empty */ + | radv_iface_opts radv_iface_item ';' + ; + +radv_iface_opt_list: + /* empty */ + | '{' radv_iface_opts '}' + ; + +radv_iface: + radv_iface_start iface_patt_list radv_iface_opt_list radv_iface_finish; + + +radv_prefix_start: prefix +{ + this_radv_prefix = cfg_allocz(sizeof(struct radv_prefix_config)); + RADV_PREFIX->prefix = $1.addr; + RADV_PREFIX->pxlen = $1.len; + + RADV_PREFIX->onlink = 1; + RADV_PREFIX->autonomous = 1; + RADV_PREFIX->valid_lifetime = DEFAULT_VALID_LIFETIME; + RADV_PREFIX->preferred_lifetime = DEFAULT_PREFERRED_LIFETIME; +}; + +radv_prefix_item: + SKIP bool { RADV_PREFIX->skip = $2; } + | ONLINK bool { RADV_PREFIX->onlink = $2; } + | AUTONOMOUS bool { RADV_PREFIX->autonomous = $2; } + | VALID LIFETIME expr { RADV_PREFIX->valid_lifetime = $3; if ($3 < 0) cf_error("Valid lifetime must be 0 or positive"); } + | PREFERRED LIFETIME expr { RADV_PREFIX->preferred_lifetime = $3; if ($3 < 0) cf_error("Preferred lifetime must be 0 or positive"); } + ; + +radv_prefix_finish: +{ + if (RADV_PREFIX->preferred_lifetime > RADV_PREFIX->valid_lifetime) + cf_error("Preferred lifetime must be at most Valid lifetime"); +}; + +radv_prefix_opts: + /* empty */ + | radv_prefix_opts radv_prefix_item ';' + ; + +radv_prefix_opt_list: + /* empty */ + | '{' radv_prefix_opts '}' + ; + +radv_prefix: + radv_prefix_start radv_prefix_opt_list radv_prefix_finish; + + +CF_CODE + +CF_END diff --git a/proto/radv/packets.c b/proto/radv/packets.c new file mode 100644 index 00000000..8aa487b0 --- /dev/null +++ b/proto/radv/packets.c @@ -0,0 +1,268 @@ +/* + * BIRD -- RAdv Packet Processing + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +#include +#include "radv.h" + +struct radv_ra_packet +{ + u8 type; + u8 code; + u16 checksum; + u8 current_hop_limit; + u8 flags; + u16 router_lifetime; + u32 reachable_time; + u32 retrans_timer; +}; + +#define OPT_RA_MANAGED 0x80 +#define OPT_RA_OTHER_CFG 0x40 + +#define OPT_PREFIX 3 +#define OPT_MTU 5 + +struct radv_opt_prefix +{ + u8 type; + u8 length; + u8 pxlen; + u8 flags; + u32 valid_lifetime; + u32 preferred_lifetime; + u32 reserved; + ip_addr prefix; +}; + +#define OPT_PX_ONLINK 0x80 +#define OPT_PX_AUTONOMOUS 0x40 + +struct radv_opt_mtu +{ + u8 type; + u8 length; + u16 reserved; + u32 mtu; +}; + +static struct radv_prefix_config default_prefix = { + .onlink = 1, + .autonomous = 1, + .valid_lifetime = DEFAULT_VALID_LIFETIME, + .preferred_lifetime = DEFAULT_PREFERRED_LIFETIME +}; + +static struct radv_prefix_config * +radv_prefix_match(struct radv_iface *ifa, struct ifa *a) +{ + struct proto *p = &ifa->ra->p; + struct radv_config *cf = (struct radv_config *) (p->cf); + struct radv_prefix_config *pc; + + if (a->scope <= SCOPE_LINK) + return NULL; + + WALK_LIST(pc, ifa->cf->pref_list) + if ((a->pxlen >= pc->pxlen) && ipa_in_net(a->prefix, pc->prefix, pc->pxlen)) + return pc; + + WALK_LIST(pc, cf->pref_list) + if ((a->pxlen >= pc->pxlen) && ipa_in_net(a->prefix, pc->prefix, pc->pxlen)) + return pc; + + return &default_prefix; +} + +static void +radv_prepare_ra(struct radv_iface *ifa) +{ + struct proto_radv *ra = ifa->ra; + + char *buf = ifa->sk->tbuf; + char *bufstart = buf; + char *bufend = buf + ifa->sk->tbsize; + + struct radv_ra_packet *pkt = (void *) buf; + pkt->type = ICMPV6_RA; + pkt->code = 0; + pkt->checksum = 0; + pkt->current_hop_limit = ifa->cf->current_hop_limit; + pkt->flags = (ifa->cf->managed ? OPT_RA_MANAGED : 0) | + (ifa->cf->other_config ? OPT_RA_OTHER_CFG : 0); + pkt->router_lifetime = htons(ifa->cf->default_lifetime); + pkt->reachable_time = htonl(ifa->cf->reachable_time); + pkt->retrans_timer = htonl(ifa->cf->retrans_timer); + buf += sizeof(*pkt); + + if (ifa->cf->link_mtu) + { + struct radv_opt_mtu *om = (void *) buf; + om->type = OPT_MTU; + om->length = 1; + om->reserved = 0; + om->mtu = htonl(ifa->cf->link_mtu); + buf += sizeof (*om); + } + + struct ifa *addr; + WALK_LIST(addr, ifa->iface->addrs) + { + struct radv_prefix_config *pc; + pc = radv_prefix_match(ifa, addr); + + if (!pc || pc->skip) + continue; + + if (buf + sizeof(struct radv_opt_prefix) > bufend) + { + log(L_WARN "%s: Too many prefixes on interface %s", ra->p.name, ifa->iface->name); + break; + } + + struct radv_opt_prefix *op = (void *) buf; + op->type = OPT_PREFIX; + op->length = 4; + op->pxlen = addr->pxlen; + op->flags = (pc->onlink ? OPT_PX_ONLINK : 0) | + (pc->autonomous ? OPT_PX_AUTONOMOUS : 0); + op->valid_lifetime = htonl(pc->valid_lifetime); + op->preferred_lifetime = htonl(pc->preferred_lifetime); + op->reserved = 0; + op->prefix = addr->prefix; + ipa_hton(op->prefix); + buf += sizeof(*op); + } + + ifa->plen = buf - bufstart; +} + + +void +radv_send_ra(struct radv_iface *ifa, int shutdown) +{ + struct proto_radv *ra = ifa->ra; + + /* We store prepared RA in tbuf */ + if (!ifa->plen) + radv_prepare_ra(ifa); + + if (shutdown) + { + /* Modify router lifetime to 0, it is not restored because + we suppose that the iface will be removed */ + struct radv_ra_packet *pkt = (void *) ifa->sk->tbuf; + pkt->router_lifetime = 0; + } + + RADV_TRACE(D_PACKETS, "Sending RA via %s", ifa->iface->name); + sk_send_to(ifa->sk, ifa->plen, AllNodes, 0); +} + + +static int +radv_rx_hook(sock *sk, int size) +{ + struct radv_iface *ifa = sk->data; + struct proto_radv *ra = ifa->ra; + + /* We want just packets from sk->iface */ + if (sk->lifindex != sk->iface->index) + return 1; + + if (ipa_equal(sk->faddr, ifa->addr->ip)) + return 1; + + if (size < 8) + return 1; + + byte *buf = sk->rbuf; + + if (buf[1] != 0) + return 1; + + /* Validation is a bit sloppy - Hop Limit is not checked and + length of options is ignored for RS and left to later for RA */ + + switch (buf[0]) + { + case ICMPV6_RS: + RADV_TRACE(D_PACKETS, "Received RS from %I via %s", + sk->faddr, ifa->iface->name); + radv_iface_notify(ifa, RA_EV_RS); + return 1; + + case ICMPV6_RA: + RADV_TRACE(D_PACKETS, "Received RA from %I via %s", + sk->faddr, ifa->iface->name); + /* FIXME - there should be some checking of received RAs, but we just ignore them */ + return 1; + + default: + return 1; + } +} + +static void +radv_tx_hook(sock *sk) +{ + struct radv_iface *ifa = sk->data; + log(L_WARN "%s: TX hook called", ifa->ra->p.name); +} + +static void +radv_err_hook(sock *sk, int err) +{ + struct radv_iface *ifa = sk->data; + log(L_ERR "%s: Socket error: %m", ifa->ra->p.name, err); +} + +int +radv_sk_open(struct radv_iface *ifa) +{ + sock *sk = sk_new(ifa->ra->p.pool); + sk->type = SK_IP; + sk->dport = ICMPV6_PROTO; + sk->saddr = IPA_NONE; + + sk->ttl = 255; /* Mandatory for Neighbor Discovery packets */ + sk->rx_hook = radv_rx_hook; + sk->tx_hook = radv_tx_hook; + sk->err_hook = radv_err_hook; + sk->iface = ifa->iface; + sk->rbsize = 1024; // bufsize(ifa); + sk->tbsize = 1024; // bufsize(ifa); + sk->data = ifa; + sk->flags = SKF_LADDR_RX; + + if (sk_open(sk) != 0) + goto err; + + sk->saddr = ifa->addr->ip; + + /* 2 is an offset of the checksum in an ICMPv6 packet */ + if (sk_set_ipv6_checksum(sk, 2) < 0) + goto err; + + /* We want listen just to ICMPv6 messages of type RS and RA */ + if (sk_set_icmp_filter(sk, ICMPV6_RS, ICMPV6_RA) < 0) + goto err; + + if (sk_setup_multicast(sk) < 0) + goto err; + + if (sk_join_group(sk, AllRouters) < 0) + goto err; + + ifa->sk = sk; + return 1; + + err: + rfree(sk); + return 0; +} + diff --git a/proto/radv/radv.c b/proto/radv/radv.c new file mode 100644 index 00000000..01cb6899 --- /dev/null +++ b/proto/radv/radv.c @@ -0,0 +1,329 @@ +/* + * BIRD -- Router Advertisement + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + + +#include +#include "radv.h" + +/** + * DOC: Router Advertisements + * + * The RAdv protocol is implemented in two files: |radv.c| containing + * the interface with BIRD core and the protocol logic and |packets.c| + * handling low level protocol stuff (RX, TX and packet formats). + * The protocol does not import or export any routes. + * + * The RAdv is structured in the usual way - for each handled interface + * there is a structure &radv_iface that contains a state related to + * that interface together with its resources (a socket, a timer). + * There is also a prepared RA stored in a TX buffer of the socket + * associated with an iface. These iface structures are created + * and removed according to iface events from BIRD core handled by + * radv_if_notify() callback. + * + * The main logic of RAdv consists of two functions: + * radv_iface_notify(), which processes asynchronous events (specified + * by RA_EV_* codes), and radv_timer(), which triggers sending RAs and + * computes the next timeout. + */ + +static void +radv_timer(timer *tm) +{ + struct radv_iface *ifa = tm->data; + struct proto_radv *ra = ifa->ra; + + RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name); + + radv_send_ra(ifa, 0); + + /* Update timer */ + ifa->last = now; + unsigned after = ifa->cf->min_ra_int; + after += random() % (ifa->cf->max_ra_int - ifa->cf->min_ra_int + 1); + + if (ifa->initial) + ifa->initial--; + + if (ifa->initial) + after = MIN(after, MAX_INITIAL_RTR_ADVERT_INTERVAL); + + tm_start(ifa->timer, after); +} + +static char* ev_name[] = { NULL, "Init", "Change", "RS" }; + +void +radv_iface_notify(struct radv_iface *ifa, int event) +{ + struct proto_radv *ra = ifa->ra; + + if (!ifa->sk) + return; + + RADV_TRACE(D_EVENTS, "Event %s on %s", ev_name[event], ifa->iface->name); + + switch (event) + { + case RA_EV_CHANGE: + ifa->plen = 0; + case RA_EV_INIT: + ifa->initial = MAX_INITIAL_RTR_ADVERTISEMENTS; + break; + + case RA_EV_RS: + break; + } + + /* Update timer */ + unsigned delta = now - ifa->last; + unsigned after = 0; + + if (delta < ifa->cf->min_delay) + after = ifa->cf->min_delay - delta; + + tm_start(ifa->timer, after); +} + +static struct radv_iface * +radv_iface_find(struct proto_radv *ra, struct iface *what) +{ + struct radv_iface *ifa; + + WALK_LIST(ifa, ra->iface_list) + if (ifa->iface == what) + return ifa; + + return NULL; +} + +static void +radv_iface_add(struct object_lock *lock) +{ + struct radv_iface *ifa = lock->data; + struct proto_radv *ra = ifa->ra; + + if (! radv_sk_open(ifa)) + { + log(L_ERR "%s: Socket open failed on interface %s", ra->p.name, ifa->iface->name); + return; + } + + radv_iface_notify(ifa, RA_EV_INIT); +} + +static inline struct ifa * +find_lladdr(struct iface *iface) +{ + struct ifa *a; + WALK_LIST(a, iface->addrs) + if (a->scope == SCOPE_LINK) + return a; + + return NULL; +} + +static void +radv_iface_new(struct proto_radv *ra, struct iface *iface, struct radv_iface_config *cf) +{ + pool *pool = ra->p.pool; + struct radv_iface *ifa; + + RADV_TRACE(D_EVENTS, "Adding interface %s", iface->name); + + ifa = mb_allocz(pool, sizeof(struct radv_iface)); + ifa->ra = ra; + ifa->cf = cf; + ifa->iface = iface; + + add_tail(&ra->iface_list, NODE ifa); + + ifa->addr = find_lladdr(iface); + if (!ifa->addr) + { + log(L_ERR "%s: Cannot find link-locad addr on interface %s", ra->p.name, iface->name); + return; + } + + timer *tm = tm_new(pool); + tm->hook = radv_timer; + tm->data = ifa; + tm->randomize = 0; + tm->recurrent = 0; + ifa->timer = tm; + + struct object_lock *lock = olock_new(pool); + lock->addr = IPA_NONE; + lock->type = OBJLOCK_IP; + lock->port = ICMPV6_PROTO; + lock->iface = iface; + lock->data = ifa; + lock->hook = radv_iface_add; + ifa->lock = lock; + + olock_acquire(lock); +} + +static void +radv_iface_remove(struct radv_iface *ifa) +{ + struct proto_radv *ra = ifa->ra; + RADV_TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name); + + rem_node(NODE ifa); + + rfree(ifa->sk); + rfree(ifa->timer); + rfree(ifa->lock); + + mb_free(ifa); +} + +static void +radv_if_notify(struct proto *p, unsigned flags, struct iface *iface) +{ + struct proto_radv *ra = (struct proto_radv *) p; + struct radv_config *cf = (struct radv_config *) (p->cf); + + if (iface->flags & IF_IGNORE) + return; + + if (flags & IF_CHANGE_UP) + { + struct radv_iface_config *ic = (struct radv_iface_config *) + iface_patt_find(&cf->patt_list, iface, NULL); + + if (ic) + radv_iface_new(ra, iface, ic); + + return; + } + + struct radv_iface *ifa = radv_iface_find(ra, iface); + if (!ifa) + return; + + if (flags & IF_CHANGE_DOWN) + { + radv_iface_remove(ifa); + return; + } + + if ((flags & IF_CHANGE_LINK) && (iface->flags & IF_LINK_UP)) + radv_iface_notify(ifa, RA_EV_INIT); +} + +static void +radv_ifa_notify(struct proto *p, unsigned flags, struct ifa *a) +{ + struct proto_radv *ra = (struct proto_radv *) p; + + if (a->flags & IA_SECONDARY) + return; + + if (a->scope <= SCOPE_LINK) + return; + + struct radv_iface *ifa = radv_iface_find(ra, a->iface); + + if (ifa) + radv_iface_notify(ifa, RA_EV_CHANGE); +} + +static struct proto * +radv_init(struct proto_config *c) +{ + struct proto *p = proto_new(c, sizeof(struct proto_radv)); + + p->if_notify = radv_if_notify; + p->ifa_notify = radv_ifa_notify; + return p; +} + +static int +radv_start(struct proto *p) +{ + struct proto_radv *ra = (struct proto_radv *) p; + // struct radv_config *cf = (struct radv_config *) (p->cf); + + init_list(&(ra->iface_list)); + + return PS_UP; +} + +static inline void +radv_iface_shutdown(struct radv_iface *ifa) +{ + if (ifa->sk) + radv_send_ra(ifa, 1); +} + +static int +radv_shutdown(struct proto *p) +{ + struct proto_radv *ra = (struct proto_radv *) p; + + struct radv_iface *ifa; + WALK_LIST(ifa, ra->iface_list) + radv_iface_shutdown(ifa); + + return PS_DOWN; +} + +static int +radv_reconfigure(struct proto *p, struct proto_config *c) +{ + struct proto_radv *ra = (struct proto_radv *) p; + // struct radv_config *old = (struct radv_config *) (p->cf); + struct radv_config *new = (struct radv_config *) c; + + /* + * The question is why there is a reconfigure function for RAdv if + * it has almost none internal state so restarting the protocol + * would probably suffice. One small reason is that restarting the + * protocol would lead to sending a RA with Router Lifetime 0 + * causing nodes to temporary remove their default routes. + */ + + struct iface *iface; + WALK_LIST(iface, iface_list) + { + struct radv_iface *ifa = radv_iface_find(ra, iface); + struct radv_iface_config *ic = (struct radv_iface_config *) + iface_patt_find(&new->patt_list, iface, NULL); + + if (ifa && ic) + { + ifa->cf = ic; + + /* We cheat here - always notify the change even if there isn't + any. That would leads just to a few unnecessary RAs. */ + radv_iface_notify(ifa, RA_EV_CHANGE); + } + + if (ifa && !ic) + { + radv_iface_shutdown(ifa); + radv_iface_remove(ifa); + } + + if (!ifa && ic) + radv_iface_new(ra, iface, ic); + } + + return 1; +} + + +struct protocol proto_radv = { + .name = "RAdv", + .template = "radv%d", + .init = radv_init, + .start = radv_start, + .shutdown = radv_shutdown, + .reconfigure = radv_reconfigure +}; diff --git a/proto/radv/radv.h b/proto/radv/radv.h new file mode 100644 index 00000000..fe121f26 --- /dev/null +++ b/proto/radv/radv.h @@ -0,0 +1,131 @@ +/* + * BIRD -- Router Advertisement + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_RADV_H_ +#define _BIRD_RADV_H_ + +#include "nest/bird.h" + +#include "lib/ip.h" +#include "lib/lists.h" +#include "lib/socket.h" +#include "lib/timer.h" +#include "lib/resource.h" +#include "nest/protocol.h" +#include "nest/iface.h" +#include "nest/route.h" +#include "nest/cli.h" +#include "nest/locks.h" +#include "conf/conf.h" +#include "lib/string.h" + + +#define ICMPV6_PROTO 58 + +#define AllNodes _MI(0xFF020000, 0, 0, 1) /* FF02::1 */ +#define AllRouters _MI(0xFF020000, 0, 0, 2) /* FF02::2 */ + +#define ICMPV6_RS 133 +#define ICMPV6_RA 134 + +#define MAX_INITIAL_RTR_ADVERTISEMENTS 3 +#define MAX_INITIAL_RTR_ADVERT_INTERVAL 16 + +#define DEFAULT_MAX_RA_INT 600 +#define DEFAULT_MIN_DELAY 3 +#define DEFAULT_CURRENT_HOP_LIMIT 64 + +#define DEFAULT_VALID_LIFETIME 86400 +#define DEFAULT_PREFERRED_LIFETIME 14400 + + +struct radv_config +{ + struct proto_config c; + list patt_list; /* List of iface configs */ + list pref_list; /* Global list of prefix configs */ +}; + +struct radv_iface_config +{ + struct iface_patt i; + list pref_list; /* Local list of prefix configs */ + + u32 min_ra_int; /* Standard options from RFC 4261 */ + u32 max_ra_int; + u32 min_delay; + + u8 managed; + u8 other_config; + u32 link_mtu; + u32 reachable_time; + u32 retrans_timer; + u32 current_hop_limit; + u32 default_lifetime; +}; + +struct radv_prefix_config +{ + node n; + ip_addr prefix; + int pxlen; + + u8 skip; /* Do not include this prefix to RA */ + u8 onlink; /* Standard options from RFC 4261 */ + u8 autonomous; + u32 valid_lifetime; + u32 preferred_lifetime; +}; + +struct proto_radv +{ + struct proto p; + list iface_list; /* List of active ifaces */ +}; + +struct radv_iface +{ + node n; + struct proto_radv *ra; + struct radv_iface_config *cf; /* Related config, must be updated in reconfigure */ + struct iface *iface; + struct ifa *addr; /* Link-local address of iface */ + + timer *timer; + struct object_lock *lock; + sock *sk; + + bird_clock_t last; /* Time of last sending of RA */ + u16 plen; /* Length of prepared RA in tbuf, or 0 if not valid */ + byte initial; /* List of active ifaces */ +}; + +#define RA_EV_INIT 1 /* Switch to initial mode */ +#define RA_EV_CHANGE 2 /* Change of options or prefixes */ +#define RA_EV_RS 3 /* Received RS */ + + + +#ifdef LOCAL_DEBUG +#define RADV_FORCE_DEBUG 1 +#else +#define RADV_FORCE_DEBUG 0 +#endif +#define RADV_TRACE(flags, msg, args...) do { if ((ra->p.debug & flags) || RADV_FORCE_DEBUG) \ + log(L_TRACE "%s: " msg, ra->p.name , ## args ); } while(0) + + +/* radv.c */ +void radv_iface_notify(struct radv_iface *ifa, int event); + +/* packets.c */ +void radv_send_ra(struct radv_iface *ifa, int shutdown); +int radv_sk_open(struct radv_iface *ifa); + + + +#endif /* _BIRD_RADV_H_ */ diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in index 66e66a84..4739fbaf 100644 --- a/sysdep/autoconf.h.in +++ b/sysdep/autoconf.h.in @@ -35,6 +35,7 @@ /* Protocols compiled in */ #undef CONFIG_STATIC #undef CONFIG_RIP +#undef CONFIG_RADV #undef CONFIG_BGP #undef CONFIG_OSPF #undef CONFIG_PIPE diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c index a552a2a6..29be9a86 100644 --- a/sysdep/unix/io.c +++ b/sysdep/unix/io.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "nest/bird.h" #include "lib/lists.h" @@ -880,6 +881,25 @@ sk_set_ipv6_checksum(sock *s, int offset) return 0; } +int +sk_set_icmp_filter(sock *s, int p1, int p2) +{ + /* a bit of lame interface, but it is here only for Radv */ + struct icmp6_filter f; + + ICMP6_FILTER_SETBLOCKALL(&f); + ICMP6_FILTER_SETPASS(p1, &f); + ICMP6_FILTER_SETPASS(p2, &f); + + if (setsockopt(s->fd, IPPROTO_ICMPV6, ICMP6_FILTER, &f, sizeof(f)) < 0) + { + log(L_ERR "sk_setup_icmp_filter: ICMP6_FILTER: %m"); + return -1; + } + + return 0; +} + int sk_setup_multicast(sock *s) { @@ -951,7 +971,6 @@ sk_leave_group(sock *s, ip_addr maddr) return 0; } - #else /* IPV4 */ int