1 /* dnsmasq is Copyright (c) 2000-2016 Simon Kelley
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 dated June, 1991, or
6 (at your option) version 3 dated 29 June, 2007.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include <netinet/icmp6.h>
24 struct dhcp_context
*current
;
25 struct dhcp_relay
*relay
;
26 struct in6_addr fallback
, relay_local
, ll_addr
, ula_addr
;
31 static int complete_context6(struct in6_addr
*local
, int prefix
,
32 int scope
, int if_index
, int flags
,
33 unsigned int preferred
, unsigned int valid
, void *vparam
);
34 static int make_duid1(int index
, unsigned int type
, char *mac
, size_t maclen
, void *parm
);
39 struct sockaddr_in6 saddr
;
40 #if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
41 int class = IPTOS_CLASS_CS6
;
45 if ((fd
= socket(PF_INET6
, SOCK_DGRAM
, IPPROTO_UDP
)) == -1 ||
46 #if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
47 setsockopt(fd
, IPPROTO_IPV6
, IPV6_TCLASS
, &class, sizeof(class)) == -1 ||
49 setsockopt(fd
, IPPROTO_IPV6
, IPV6_V6ONLY
, &oneopt
, sizeof(oneopt
)) == -1 ||
52 die (_("cannot create DHCPv6 socket: %s"), NULL
, EC_BADNET
);
54 /* When bind-interfaces is set, there might be more than one dnmsasq
55 instance binding port 547. That's OK if they serve different networks.
56 Need to set REUSEADDR|REUSEPORT to make this posible.
57 Handle the case that REUSEPORT is defined, but the kernel doesn't
58 support it. This handles the introduction of REUSEPORT on Linux. */
59 if (option_bool(OPT_NOWILD
) || option_bool(OPT_CLEVERBIND
))
64 if ((rc
= setsockopt(fd
, SOL_SOCKET
, SO_REUSEPORT
, &oneopt
, sizeof(oneopt
))) == -1 &&
70 rc
= setsockopt(fd
, SOL_SOCKET
, SO_REUSEADDR
, &oneopt
, sizeof(oneopt
));
73 die(_("failed to set SO_REUSE{ADDR|PORT} on DHCPv6 socket: %s"), NULL
, EC_BADNET
);
76 memset(&saddr
, 0, sizeof(saddr
));
77 #ifdef HAVE_SOCKADDR_SA_LEN
78 saddr
.sin6_len
= sizeof(struct sockaddr_in6
);
80 saddr
.sin6_family
= AF_INET6
;
81 saddr
.sin6_addr
= in6addr_any
;
82 saddr
.sin6_port
= htons(DHCPV6_SERVER_PORT
);
84 if (bind(fd
, (struct sockaddr
*)&saddr
, sizeof(struct sockaddr_in6
)))
85 die(_("failed to bind DHCPv6 server socket: %s"), NULL
, EC_BADNET
);
90 void dhcp6_packet(time_t now
)
92 struct dhcp_context
*context
;
93 struct dhcp_relay
*relay
;
94 struct iface_param parm
;
95 struct cmsghdr
*cmptr
;
99 struct cmsghdr align
; /* this ensures alignment */
100 char control6
[CMSG_SPACE(sizeof(struct in6_pktinfo
))];
102 struct sockaddr_in6 from
;
107 struct in6_addr dst_addr
;
109 memset(&dst_addr
, 0, sizeof(dst_addr
));
111 msg
.msg_control
= control_u
.control6
;
112 msg
.msg_controllen
= sizeof(control_u
);
114 msg
.msg_name
= &from
;
115 msg
.msg_namelen
= sizeof(from
);
116 msg
.msg_iov
= &daemon
->dhcp_packet
;
119 if ((sz
= recv_dhcp_packet(daemon
->dhcp6fd
, &msg
)) == -1)
122 for (cmptr
= CMSG_FIRSTHDR(&msg
); cmptr
; cmptr
= CMSG_NXTHDR(&msg
, cmptr
))
123 if (cmptr
->cmsg_level
== IPPROTO_IPV6
&& cmptr
->cmsg_type
== daemon
->v6pktinfo
)
127 struct in6_pktinfo
*p
;
129 p
.c
= CMSG_DATA(cmptr
);
131 if_index
= p
.p
->ipi6_ifindex
;
132 dst_addr
= p
.p
->ipi6_addr
;
135 if (!indextoname(daemon
->dhcp6fd
, if_index
, ifr
.ifr_name
))
138 if ((port
= relay_reply6(&from
, sz
, ifr
.ifr_name
)) == 0)
140 struct dhcp_bridge
*bridge
, *alias
;
142 for (tmp
= daemon
->if_except
; tmp
; tmp
= tmp
->next
)
143 if (tmp
->name
&& wildcard_match(tmp
->name
, ifr
.ifr_name
))
146 for (tmp
= daemon
->dhcp_except
; tmp
; tmp
= tmp
->next
)
147 if (tmp
->name
&& wildcard_match(tmp
->name
, ifr
.ifr_name
))
152 memset(&parm
.relay_local
, 0, IN6ADDRSZ
);
155 memset(&parm
.fallback
, 0, IN6ADDRSZ
);
156 memset(&parm
.ll_addr
, 0, IN6ADDRSZ
);
157 memset(&parm
.ula_addr
, 0, IN6ADDRSZ
);
159 /* If the interface on which the DHCPv6 request was received is
160 an alias of some other interface (as specified by the
161 --bridge-interface option), change parm.ind so that we look
162 for DHCPv6 contexts associated with the aliased interface
163 instead of with the aliasing one. */
164 for (bridge
= daemon
->bridges
; bridge
; bridge
= bridge
->next
)
166 for (alias
= bridge
->alias
; alias
; alias
= alias
->next
)
167 if (wildcard_matchn(alias
->iface
, ifr
.ifr_name
, IF_NAMESIZE
))
169 parm
.ind
= if_nametoindex(bridge
->iface
);
172 my_syslog(MS_DHCP
| LOG_WARNING
,
173 _("unknown interface %s in bridge-interface"),
183 for (context
= daemon
->dhcp6
; context
; context
= context
->next
)
184 if (IN6_IS_ADDR_UNSPECIFIED(&context
->start6
) && context
->prefix
== 0)
186 /* wildcard context for DHCP-stateless only */
187 parm
.current
= context
;
188 context
->current
= NULL
;
192 /* unlinked contexts are marked by context->current == context */
193 context
->current
= context
;
194 memset(&context
->local6
, 0, IN6ADDRSZ
);
197 for (relay
= daemon
->relay6
; relay
; relay
= relay
->next
)
198 relay
->current
= relay
;
200 if (!iface_enumerate(AF_INET6
, &parm
, complete_context6
))
203 if (daemon
->if_names
|| daemon
->if_addrs
)
206 for (tmp
= daemon
->if_names
; tmp
; tmp
= tmp
->next
)
207 if (tmp
->name
&& wildcard_match(tmp
->name
, ifr
.ifr_name
))
210 if (!tmp
&& !parm
.addr_match
)
216 /* Ignore requests sent to the ALL_SERVERS multicast address for relay when
217 we're listening there for DHCPv6 server reasons. */
218 struct in6_addr all_servers
;
220 inet_pton(AF_INET6
, ALL_SERVERS
, &all_servers
);
222 if (!IN6_ARE_ADDR_EQUAL(&dst_addr
, &all_servers
))
223 relay_upstream6(parm
.relay
, sz
, &from
.sin6_addr
, from
.sin6_scope_id
, now
);
227 /* May have configured relay, but not DHCP server */
228 if (!daemon
->doing_dhcp6
)
231 lease_prune(NULL
, now
); /* lose any expired leases */
233 port
= dhcp6_reply(parm
.current
, if_index
, ifr
.ifr_name
, &parm
.fallback
,
234 &parm
.ll_addr
, &parm
.ula_addr
, sz
, &from
.sin6_addr
, now
);
236 lease_update_file(now
);
240 /* The port in the source address of the original request should
241 be correct, but at least once client sends from the server port,
242 so we explicitly send to the client port to a client, and the
243 server port to a relay. */
246 from
.sin6_port
= htons(port
);
247 while (retry_send(sendto(daemon
->dhcp6fd
, daemon
->outpacket
.iov_base
,
248 save_counter(0), 0, (struct sockaddr
*)&from
,
253 void get_client_mac(struct in6_addr
*client
, int iface
, unsigned char *mac
, unsigned int *maclenp
, unsigned int *mactypep
, time_t now
)
255 /* Recieving a packet from a host does not populate the neighbour
256 cache, so we send a neighbour discovery request if we can't
257 find the sender. Repeat a few times in case of packet loss. */
259 struct neigh_packet neigh
;
260 union mysockaddr addr
;
263 neigh
.type
= ND_NEIGHBOR_SOLICIT
;
266 neigh
.target
= *client
;
267 /* RFC4443 section-2.3: checksum has to be zero to be calculated */
270 memset(&addr
, 0, sizeof(addr
));
271 #ifdef HAVE_SOCKADDR_SA_LEN
272 addr
.in6
.sin6_len
= sizeof(struct sockaddr_in6
);
274 addr
.in6
.sin6_family
= AF_INET6
;
275 addr
.in6
.sin6_port
= htons(IPPROTO_ICMPV6
);
276 addr
.in6
.sin6_addr
= *client
;
277 addr
.in6
.sin6_scope_id
= iface
;
279 for (i
= 0; i
< 5; i
++)
283 if ((maclen
= find_mac(&addr
, mac
, 0, now
)) != 0)
286 sendto(daemon
->icmp6fd
, &neigh
, sizeof(neigh
), 0, &addr
.sa
, sizeof(addr
));
289 ts
.tv_nsec
= 100000000; /* 100ms */
290 nanosleep(&ts
, NULL
);
294 *mactypep
= ARPHRD_ETHER
;
297 static int complete_context6(struct in6_addr
*local
, int prefix
,
298 int scope
, int if_index
, int flags
, unsigned int preferred
,
299 unsigned int valid
, void *vparam
)
301 struct dhcp_context
*context
;
302 struct dhcp_relay
*relay
;
303 struct iface_param
*param
= vparam
;
306 (void)scope
; /* warning */
308 if (if_index
== param
->ind
)
310 if (IN6_IS_ADDR_LINKLOCAL(local
))
311 param
->ll_addr
= *local
;
312 else if (IN6_IS_ADDR_ULA(local
))
313 param
->ula_addr
= *local
;
315 if (!IN6_IS_ADDR_LOOPBACK(local
) &&
316 !IN6_IS_ADDR_LINKLOCAL(local
) &&
317 !IN6_IS_ADDR_MULTICAST(local
))
319 /* if we have --listen-address config, see if the
320 arrival interface has a matching address. */
321 for (tmp
= daemon
->if_addrs
; tmp
; tmp
= tmp
->next
)
322 if (tmp
->addr
.sa
.sa_family
== AF_INET6
&&
323 IN6_ARE_ADDR_EQUAL(&tmp
->addr
.in6
.sin6_addr
, local
))
324 param
->addr_match
= 1;
326 /* Determine a globally address on the arrival interface, even
327 if we have no matching dhcp-context, because we're only
328 allocating on remote subnets via relays. This
329 is used as a default for the DNS server option. */
330 param
->fallback
= *local
;
332 for (context
= daemon
->dhcp6
; context
; context
= context
->next
)
334 if ((context
->flags
& CONTEXT_DHCP
) &&
335 !(context
->flags
& (CONTEXT_TEMPLATE
| CONTEXT_OLD
)) &&
336 prefix
<= context
->prefix
&&
337 is_same_net6(local
, &context
->start6
, context
->prefix
) &&
338 is_same_net6(local
, &context
->end6
, context
->prefix
))
342 /* link it onto the current chain if we've not seen it before */
343 if (context
->current
== context
)
345 struct dhcp_context
*tmp
, **up
;
347 /* use interface values only for contructed contexts */
348 if (!(context
->flags
& CONTEXT_CONSTRUCTED
))
349 preferred
= valid
= 0xffffffff;
350 else if (flags
& IFACE_DEPRECATED
)
353 if (context
->flags
& CONTEXT_DEPRECATE
)
356 /* order chain, longest preferred time first */
357 for (up
= ¶m
->current
, tmp
= param
->current
; tmp
; tmp
= tmp
->current
)
358 if (tmp
->preferred
<= preferred
)
363 context
->current
= *up
;
365 context
->local6
= *local
;
366 context
->preferred
= preferred
;
367 context
->valid
= valid
;
373 for (relay
= daemon
->relay6
; relay
; relay
= relay
->next
)
374 if (IN6_ARE_ADDR_EQUAL(local
, &relay
->local
.addr
.addr6
) && relay
->current
== relay
&&
375 (IN6_IS_ADDR_UNSPECIFIED(¶m
->relay_local
) || IN6_ARE_ADDR_EQUAL(local
, ¶m
->relay_local
)))
377 relay
->current
= param
->relay
;
378 param
->relay
= relay
;
379 param
->relay_local
= *local
;
387 struct dhcp_config
*config_find_by_address6(struct dhcp_config
*configs
, struct in6_addr
*net
, int prefix
, u64 addr
)
389 struct dhcp_config
*config
;
391 for (config
= configs
; config
; config
= config
->next
)
392 if ((config
->flags
& CONFIG_ADDR6
) &&
393 is_same_net6(&config
->addr6
, net
, prefix
) &&
394 (prefix
== 128 || addr6part(&config
->addr6
) == addr
))
400 struct dhcp_context
*address6_allocate(struct dhcp_context
*context
, unsigned char *clid
, int clid_len
, int temp_addr
,
401 int iaid
, int serial
, struct dhcp_netid
*netids
, int plain_range
, struct in6_addr
*ans
)
403 /* Find a free address: exclude anything in use and anything allocated to
404 a particular hwaddr/clientid/hostname in our configuration.
405 Try to return from contexts which match netids first.
407 Note that we assume the address prefix lengths are 64 or greater, so we can
408 get by with 64 bit arithmetic.
412 struct dhcp_context
*c
, *d
;
416 /* hash hwaddr: use the SDBM hashing algorithm. This works
417 for MAC addresses, let's see how it manages with client-ids!
418 For temporary addresses, we generate a new random one each time. */
422 for (j
= iaid
, i
= 0; i
< clid_len
; i
++)
423 j
= clid
[i
] + (j
<< 6) + (j
<< 16) - j
;
425 for (pass
= 0; pass
<= plain_range
? 1 : 0; pass
++)
426 for (c
= context
; c
; c
= c
->current
)
427 if (c
->flags
& (CONTEXT_DEPRECATE
| CONTEXT_STATIC
| CONTEXT_RA_STATELESS
| CONTEXT_USED
))
429 else if (!match_netid(c
->filter
, netids
, pass
))
433 if (!temp_addr
&& option_bool(OPT_CONSEC_ADDR
))
434 /* seed is largest extant lease addr in this context */
435 start
= lease_find_max_addr6(c
) + serial
;
438 u64 range
= 1 + addr6part(&c
->end6
) - addr6part(&c
->start6
);
439 u64 offset
= j
+ c
->addr_epoch
;
441 /* don't divide by zero if range is whole 2^64 */
443 offset
= offset
% range
;
445 start
= addr6part(&c
->start6
) + offset
;
448 /* iterate until we find a free address. */
452 /* eliminate addresses in use by the server. */
453 for (d
= context
; d
; d
= d
->current
)
454 if (addr
== addr6part(&d
->local6
))
458 !lease6_find_by_addr(&c
->start6
, c
->prefix
, addr
) &&
459 !config_find_by_address6(daemon
->dhcp_conf
, &c
->start6
, c
->prefix
, addr
))
462 setaddr6part (ans
, addr
);
468 if (addr
== addr6part(&c
->end6
) + 1)
469 addr
= addr6part(&c
->start6
);
471 } while (addr
!= start
);
477 /* can dynamically allocate addr */
478 struct dhcp_context
*address6_available(struct dhcp_context
*context
,
479 struct in6_addr
*taddr
,
480 struct dhcp_netid
*netids
,
483 u64 start
, end
, addr
= addr6part(taddr
);
484 struct dhcp_context
*tmp
;
486 for (tmp
= context
; tmp
; tmp
= tmp
->current
)
488 start
= addr6part(&tmp
->start6
);
489 end
= addr6part(&tmp
->end6
);
491 if (!(tmp
->flags
& (CONTEXT_STATIC
| CONTEXT_RA_STATELESS
)) &&
492 is_same_net6(&tmp
->start6
, taddr
, tmp
->prefix
) &&
493 is_same_net6(&tmp
->end6
, taddr
, tmp
->prefix
) &&
496 match_netid(tmp
->filter
, netids
, plain_range
))
503 /* address OK if configured */
504 struct dhcp_context
*address6_valid(struct dhcp_context
*context
,
505 struct in6_addr
*taddr
,
506 struct dhcp_netid
*netids
,
509 struct dhcp_context
*tmp
;
511 for (tmp
= context
; tmp
; tmp
= tmp
->current
)
512 if (is_same_net6(&tmp
->start6
, taddr
, tmp
->prefix
) &&
513 match_netid(tmp
->filter
, netids
, plain_range
))
519 int config_valid(struct dhcp_config
*config
, struct dhcp_context
*context
, struct in6_addr
*addr
)
521 if (!config
|| !(config
->flags
& CONFIG_ADDR6
))
524 if ((config
->flags
& CONFIG_WILDCARD
) && context
->prefix
== 64)
526 *addr
= context
->start6
;
527 setaddr6part(addr
, addr6part(&config
->addr6
));
531 if (is_same_net6(&context
->start6
, &config
->addr6
, context
->prefix
))
533 *addr
= config
->addr6
;
540 void make_duid(time_t now
)
544 if (daemon
->duid_config
)
548 daemon
->duid
= p
= safe_malloc(daemon
->duid_config_len
+ 6);
549 daemon
->duid_len
= daemon
->duid_config_len
+ 6;
550 PUTSHORT(2, p
); /* DUID_EN */
551 PUTLONG(daemon
->duid_enterprise
, p
);
552 memcpy(p
, daemon
->duid_config
, daemon
->duid_config_len
);
558 /* If we have no persistent lease database, or a non-stable RTC, use DUID_LL (newnow == 0) */
559 #ifndef HAVE_BROKEN_RTC
560 /* rebase epoch to 1/1/2000 */
561 if (!option_bool(OPT_LEASE_RO
) || daemon
->lease_change_command
)
562 newnow
= now
- 946684800;
565 iface_enumerate(AF_LOCAL
, &newnow
, make_duid1
);
568 die("Cannot create DHCPv6 server DUID: %s", NULL
, EC_MISC
);
572 static int make_duid1(int index
, unsigned int type
, char *mac
, size_t maclen
, void *parm
)
574 /* create DUID as specified in RFC3315. We use the MAC of the
575 first interface we find that isn't loopback or P-to-P and
576 has address-type < 256. Address types above 256 are things like
577 tunnels which don't have usable MAC addresses. */
582 time_t newnow
= *((time_t *)parm
);
589 daemon
->duid
= p
= safe_malloc(maclen
+ 4);
590 daemon
->duid_len
= maclen
+ 4;
591 PUTSHORT(3, p
); /* DUID_LL */
592 PUTSHORT(type
, p
); /* address type */
596 daemon
->duid
= p
= safe_malloc(maclen
+ 8);
597 daemon
->duid_len
= maclen
+ 8;
598 PUTSHORT(1, p
); /* DUID_LLT */
599 PUTSHORT(type
, p
); /* address type */
600 PUTLONG(*((time_t *)parm
), p
); /* time */
603 memcpy(p
, mac
, maclen
);
613 static int construct_worker(struct in6_addr
*local
, int prefix
,
614 int scope
, int if_index
, int flags
,
615 int preferred
, int valid
, void *vparam
)
617 char ifrn_name
[IFNAMSIZ
];
618 struct in6_addr start6
, end6
;
619 struct dhcp_context
*template, *context
;
626 struct cparam
*param
= vparam
;
628 if (IN6_IS_ADDR_LOOPBACK(local
) ||
629 IN6_IS_ADDR_LINKLOCAL(local
) ||
630 IN6_IS_ADDR_MULTICAST(local
))
633 if (!(flags
& IFACE_PERMANENT
))
636 if (flags
& IFACE_DEPRECATED
)
639 if (!indextoname(daemon
->icmp6fd
, if_index
, ifrn_name
))
642 for (template = daemon
->dhcp6
; template; template = template->next
)
643 if (!(template->flags
& CONTEXT_TEMPLATE
))
645 /* non-template entries, just fill in interface and local addresses */
646 if (prefix
<= template->prefix
&&
647 is_same_net6(local
, &template->start6
, template->prefix
) &&
648 is_same_net6(local
, &template->end6
, template->prefix
))
650 template->if_index
= if_index
;
651 template->local6
= *local
;
655 else if (wildcard_match(template->template_interface
, ifrn_name
) &&
656 template->prefix
>= prefix
)
659 setaddr6part(&start6
, addr6part(&template->start6
));
661 setaddr6part(&end6
, addr6part(&template->end6
));
663 for (context
= daemon
->dhcp6
; context
; context
= context
->next
)
664 if ((context
->flags
& CONTEXT_CONSTRUCTED
) &&
665 IN6_ARE_ADDR_EQUAL(&start6
, &context
->start6
) &&
666 IN6_ARE_ADDR_EQUAL(&end6
, &context
->end6
))
668 int flags
= context
->flags
;
669 context
->flags
&= ~(CONTEXT_GC
| CONTEXT_OLD
);
670 if (flags
& CONTEXT_OLD
)
672 /* address went, now it's back */
673 log_context(AF_INET6
, context
);
674 /* fast RAs for a while */
675 ra_start_unsolicted(param
->now
, context
);
677 /* Add address to name again */
678 if (context
->flags
& CONTEXT_RA_NAME
)
684 if (!context
&& (context
= whine_malloc(sizeof (struct dhcp_context
))))
686 *context
= *template;
687 context
->start6
= start6
;
688 context
->end6
= end6
;
689 context
->flags
&= ~CONTEXT_TEMPLATE
;
690 context
->flags
|= CONTEXT_CONSTRUCTED
;
691 context
->if_index
= if_index
;
692 context
->local6
= *local
;
693 context
->saved_valid
= 0;
695 context
->next
= daemon
->dhcp6
;
696 daemon
->dhcp6
= context
;
698 ra_start_unsolicted(param
->now
, context
);
699 /* we created a new one, need to call
700 lease_update_file to get periodic functions called */
703 /* Will need to add new putative SLAAC addresses to existing leases */
704 if (context
->flags
& CONTEXT_RA_NAME
)
707 log_context(AF_INET6
, context
);
714 void dhcp_construct_contexts(time_t now
)
716 struct dhcp_context
*context
, *tmp
, **up
;
722 for (context
= daemon
->dhcp6
; context
; context
= context
->next
)
723 if (context
->flags
& CONTEXT_CONSTRUCTED
)
724 context
->flags
|= CONTEXT_GC
;
726 iface_enumerate(AF_INET6
, ¶m
, construct_worker
);
728 for (up
= &daemon
->dhcp6
, context
= daemon
->dhcp6
; context
; context
= tmp
)
733 if (context
->flags
& CONTEXT_GC
&& !(context
->flags
& CONTEXT_OLD
))
735 if ((context
->flags
& CONTEXT_RA
) || option_bool(OPT_RA
))
737 /* previously constructed context has gone. advertise it's demise */
738 context
->flags
|= CONTEXT_OLD
;
739 context
->address_lost_time
= now
;
740 /* Apply same ceiling of configured lease time as in radv.c */
741 if (context
->saved_valid
> context
->lease_time
)
742 context
->saved_valid
= context
->lease_time
;
743 /* maximum time is 2 hours, from RFC */
744 if (context
->saved_valid
> 7200) /* 2 hours */
745 context
->saved_valid
= 7200;
746 ra_start_unsolicted(now
, context
);
747 param
.newone
= 1; /* include deletion */
749 if (context
->flags
& CONTEXT_RA_NAME
)
752 log_context(AF_INET6
, context
);
758 /* we were never doing RA for this, so free now */
769 if (daemon
->dhcp
|| daemon
->doing_dhcp6
)
772 lease_update_slaac(now
);
773 lease_update_file(now
);
776 /* Not doing DHCP, so no lease system, manage alarms for ra only */
777 send_alarm(periodic_ra(now
), now
);