Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / src / slaac.c
1 /* dnsmasq is Copyright (c) 2000-2022 Simon Kelley
2
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.
7  
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.
12      
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/>.
15 */
16
17 #include "dnsmasq.h"
18
19 #ifdef HAVE_DHCP6
20
21 #include <netinet/icmp6.h>
22
23 static int ping_id = 0;
24
25 void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
26 {
27   struct slaac_address *slaac, *old, **up;
28   struct dhcp_context *context;
29   int dns_dirty = 0;
30   
31   if (!(lease->flags & LEASE_HAVE_HWADDR) || 
32       (lease->flags & (LEASE_TA | LEASE_NA)) ||
33       lease->last_interface == 0 ||
34       !lease->hostname)
35     return ;
36   
37   old = lease->slaac_address;
38   lease->slaac_address = NULL;
39
40   for (context = daemon->dhcp6; context; context = context->next) 
41     if ((context->flags & CONTEXT_RA_NAME) && 
42         !(context->flags & CONTEXT_OLD) &&
43         lease->last_interface == context->if_index)
44       {
45         struct in6_addr addr = context->start6;
46         if (lease->hwaddr_len == 6 &&
47             (lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
48           {
49             /* convert MAC address to EUI-64 */
50             memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
51             memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
52             addr.s6_addr[11] = 0xff;
53             addr.s6_addr[12] = 0xfe;
54           }
55 #if defined(ARPHRD_EUI64)
56         else if (lease->hwaddr_len == 8 &&
57                  lease->hwaddr_type == ARPHRD_EUI64)
58           memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
59 #endif
60 #if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
61         else if (lease->clid_len == 9 && 
62                  lease->clid[0] ==  ARPHRD_EUI64 &&
63                  lease->hwaddr_type == ARPHRD_IEEE1394)
64           /* firewire has EUI-64 identifier as clid */
65           memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
66 #endif
67         else
68           continue;
69         
70         addr.s6_addr[8] ^= 0x02;
71         
72         /* check if we already have this one */
73         for (up = &old, slaac = old; slaac; slaac = slaac->next)
74           {
75             if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
76               {
77                 *up = slaac->next;
78                 /* recheck when DHCPv4 goes through init-reboot */
79                 if (force)
80                   {
81                     slaac->ping_time = now;
82                     slaac->backoff = 1;
83                     dns_dirty = 1;
84                   }
85                 break;
86               }
87             up = &slaac->next;
88           }
89             
90         /* No, make new one */
91         if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
92           {
93             slaac->ping_time = now;
94             slaac->backoff = 1;
95             slaac->addr = addr;
96             /* Do RA's to prod it */
97             ra_start_unsolicited(now, context);
98           }
99         
100         if (slaac)
101           {
102             slaac->next = lease->slaac_address;
103             lease->slaac_address = slaac;
104           }
105       }
106   
107   if (old || dns_dirty)
108     lease_update_dns(1);
109   
110   /* Free any no reused */
111   for (; old; old = slaac)
112     {
113       slaac = old->next;
114       free(old);
115     }
116 }
117
118
119 time_t periodic_slaac(time_t now, struct dhcp_lease *leases)
120 {
121   struct dhcp_context *context;
122   struct dhcp_lease *lease;
123   struct slaac_address *slaac;
124   time_t next_event = 0;
125   
126   for (context = daemon->dhcp6; context; context = context->next)
127     if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD))
128       break;
129
130   /* nothing configured */
131   if (!context)
132     return 0;
133
134   while (ping_id == 0)
135     ping_id = rand16();
136
137   for (lease = leases; lease; lease = lease->next)
138     for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
139       {
140         /* confirmed or given up? */
141         if (slaac->backoff == 0 || slaac->ping_time == 0)
142           continue;
143         
144         if (difftime(slaac->ping_time, now) <= 0.0)
145           {
146             struct ping_packet *ping;
147             struct sockaddr_in6 addr;
148  
149             reset_counter();
150
151             if (!(ping = expand(sizeof(struct ping_packet))))
152               continue;
153
154             ping->type = ICMP6_ECHO_REQUEST;
155             ping->code = 0;
156             ping->identifier = ping_id;
157             ping->sequence_no = slaac->backoff;
158             
159             memset(&addr, 0, sizeof(addr));
160 #ifdef HAVE_SOCKADDR_SA_LEN
161             addr.sin6_len = sizeof(struct sockaddr_in6);
162 #endif
163             addr.sin6_family = AF_INET6;
164             addr.sin6_port = htons(IPPROTO_ICMPV6);
165             addr.sin6_addr = slaac->addr;
166             
167             if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(-1), 0,
168                        (struct sockaddr *)&addr,  sizeof(addr)) == -1 &&
169                 errno == EHOSTUNREACH &&
170                 slaac->backoff == 12)
171               slaac->ping_time = 0; /* Give up */ 
172             else
173               {
174                 slaac->ping_time += (1 << (slaac->backoff - 1)) + (rand16()/21785); /* 0 - 3 */
175                 if (slaac->backoff > 4)
176                   slaac->ping_time += rand16()/4000; /* 0 - 15 */
177                 if (slaac->backoff < 12)
178                   slaac->backoff++;
179               }
180           }
181         
182         if (slaac->ping_time != 0 &&
183             (next_event == 0 || difftime(next_event, slaac->ping_time) >= 0.0))
184           next_event = slaac->ping_time;
185       }
186
187   return next_event;
188 }
189
190
191 void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases)
192 {
193   struct dhcp_lease *lease;
194   struct slaac_address *slaac;
195   struct ping_packet *ping = (struct ping_packet *)packet;
196   int gotone = 0;
197   
198   if (ping->identifier == ping_id)
199     for (lease = leases; lease; lease = lease->next)
200       for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
201         if (slaac->backoff != 0 && IN6_ARE_ADDR_EQUAL(sender, &slaac->addr))
202           {
203             slaac->backoff = 0;
204             gotone = 1;
205             inet_ntop(AF_INET6, sender, daemon->addrbuff, ADDRSTRLEN);
206             if (!option_bool(OPT_QUIET_DHCP6))
207               my_syslog(MS_DHCP | LOG_INFO, "SLAAC-CONFIRM(%s) %s %s", interface, daemon->addrbuff, lease->hostname); 
208           }
209   
210   lease_update_dns(gotone);
211 }
212         
213 #endif