1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "sd-dhcp-client.h"
4 #include "sd-ipv4acd.h"
5 
6 #include "networkd-address.h"
7 #include "networkd-dhcp4.h"
8 #include "networkd-ipv4acd.h"
9 #include "networkd-link.h"
10 #include "networkd-manager.h"
11 
static_ipv4acd_address_remove(Link * link,Address * address,bool on_conflict)12 static int static_ipv4acd_address_remove(Link *link, Address *address, bool on_conflict) {
13         int r;
14 
15         assert(link);
16         assert(address);
17 
18         /* Prevent form the address being freed. */
19         address_enter_probing(address);
20 
21         if (!address_exists(address))
22                 return 0; /* Not assigned. */
23 
24         if (on_conflict)
25                 log_link_warning(link, "Dropping address "IPV4_ADDRESS_FMT_STR", as an address conflict was detected.",
26                                  IPV4_ADDRESS_FMT_VAL(address->in_addr.in));
27         else
28                 log_link_debug(link, "Removing address "IPV4_ADDRESS_FMT_STR", as the ACD client is stopped.",
29                                IPV4_ADDRESS_FMT_VAL(address->in_addr.in));
30 
31         r = address_remove(address);
32         if (r < 0)
33                 return log_link_warning_errno(link, r, "Failed to remove address "IPV4_ADDRESS_FMT_STR": %m",
34                                               IPV4_ADDRESS_FMT_VAL(address->in_addr.in));
35 
36         return 0;
37 }
38 
dhcp4_address_on_conflict(Link * link,Address * address)39 static int dhcp4_address_on_conflict(Link *link, Address *address) {
40         int r;
41 
42         assert(link);
43         assert(link->dhcp_client);
44 
45         r = sd_dhcp_client_send_decline(link->dhcp_client);
46         if (r < 0)
47                 log_link_warning_errno(link, r, "Failed to send DHCP DECLINE, ignoring: %m");
48 
49         if (!link->dhcp_lease)
50                 /* Unlikely, but during probing the address, the lease may be lost. */
51                 return 0;
52 
53         log_link_warning(link, "Dropping DHCPv4 lease, as an address conflict was detected.");
54         r = dhcp4_lease_lost(link);
55         if (r < 0)
56                 return log_link_warning_errno(link, r, "Failed to drop DHCPv4 lease: %m");
57 
58         /* make the address will be freed. */
59         address_cancel_probing(address);
60 
61         /* It is not necessary to call address_remove() here, as dhcp4_lease_lost() removes it. */
62         return 0;
63 }
64 
on_acd(sd_ipv4acd * acd,int event,void * userdata)65 static void on_acd(sd_ipv4acd *acd, int event, void *userdata) {
66         Address *address = userdata;
67         Link *link;
68         int r;
69 
70         assert(acd);
71         assert(address);
72         assert(address->acd == acd);
73         assert(address->link);
74         assert(address->family == AF_INET);
75         assert(IN_SET(address->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_DHCP4));
76 
77         link = address->link;
78 
79         switch (event) {
80         case SD_IPV4ACD_EVENT_STOP:
81                 if (address->source == NETWORK_CONFIG_SOURCE_STATIC) {
82                         r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ false);
83                         if (r < 0)
84                                 link_enter_failed(link);
85                 }
86 
87                 /* We have nothing to do for DHCPv4 lease here, as the dhcp client is already stopped
88                  * when stopping the ipv4acd client. See link_stop_engines(). */
89                 break;
90 
91         case SD_IPV4ACD_EVENT_BIND:
92                 log_link_debug(link, "Successfully claimed address "IPV4_ADDRESS_FMT_STR,
93                                IPV4_ADDRESS_FMT_VAL(address->in_addr.in));
94 
95                 address_cancel_probing(address);
96                 break;
97 
98         case SD_IPV4ACD_EVENT_CONFLICT:
99                 log_link_warning(link, "Dropping address "IPV4_ADDRESS_FMT_STR", as an address conflict was detected.",
100                                  IPV4_ADDRESS_FMT_VAL(address->in_addr.in));
101 
102                 if (address->source == NETWORK_CONFIG_SOURCE_STATIC)
103                         r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ true);
104                 else
105                         r = dhcp4_address_on_conflict(link, address);
106                 if (r < 0)
107                         link_enter_failed(link);
108                 break;
109 
110         default:
111                 assert_not_reached();
112         }
113 }
114 
ipv4acd_check_mac(sd_ipv4acd * acd,const struct ether_addr * mac,void * userdata)115 static int ipv4acd_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) {
116         Manager *m = userdata;
117         struct hw_addr_data hw_addr;
118 
119         assert(m);
120         assert(mac);
121 
122         hw_addr = (struct hw_addr_data) {
123                 .length = ETH_ALEN,
124                 .ether = *mac,
125         };
126 
127         return link_get_by_hw_addr(m, &hw_addr, NULL) >= 0;
128 }
129 
ipv4acd_configure(Address * address)130 int ipv4acd_configure(Address *address) {
131         Link *link;
132         int r;
133 
134         assert(address);
135 
136         link = ASSERT_PTR(address->link);
137 
138         if (address->family != AF_INET)
139                 return 0;
140 
141         if (!FLAGS_SET(address->duplicate_address_detection, ADDRESS_FAMILY_IPV4))
142                 return 0;
143 
144         if (link->hw_addr.length != ETH_ALEN || hw_addr_is_null(&link->hw_addr))
145                 return 0;
146 
147         /* Currently, only static and DHCP4 addresses are supported. */
148         assert(IN_SET(address->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_DHCP4));
149 
150         if (address->acd) {
151                 address_enter_probing(address);
152                 return 0;
153         }
154 
155         log_link_debug(link, "Configuring IPv4ACD for address "IPV4_ADDRESS_FMT_STR,
156                        IPV4_ADDRESS_FMT_VAL(address->in_addr.in));
157 
158         r = sd_ipv4acd_new(&address->acd);
159         if (r < 0)
160                 return r;
161 
162         r = sd_ipv4acd_attach_event(address->acd, link->manager->event, 0);
163         if (r < 0)
164                 return r;
165 
166         r = sd_ipv4acd_set_ifindex(address->acd, link->ifindex);
167         if (r < 0)
168                 return r;
169 
170         r = sd_ipv4acd_set_mac(address->acd, &link->hw_addr.ether);
171         if (r < 0)
172                 return r;
173 
174         r = sd_ipv4acd_set_address(address->acd, &address->in_addr.in);
175         if (r < 0)
176                 return r;
177 
178         r = sd_ipv4acd_set_callback(address->acd, on_acd, address);
179         if (r < 0)
180                 return r;
181 
182         r = sd_ipv4acd_set_check_mac_callback(address->acd, ipv4acd_check_mac, link->manager);
183         if (r < 0)
184                 return r;
185 
186         if (link_has_carrier(link)) {
187                 r = sd_ipv4acd_start(address->acd, true);
188                 if (r < 0)
189                         return r;
190         }
191 
192         address_enter_probing(address);
193         return 0;
194 }
195 
ipv4acd_update_mac(Link * link)196 int ipv4acd_update_mac(Link *link) {
197         Address *address;
198         int k, r = 0;
199 
200         assert(link);
201 
202         if (link->hw_addr.length != ETH_ALEN)
203                 return 0;
204         if (ether_addr_is_null(&link->hw_addr.ether))
205                 return 0;
206 
207         SET_FOREACH(address, link->addresses) {
208                 if (!address->acd)
209                         continue;
210 
211                 k = sd_ipv4acd_set_mac(address->acd, &link->hw_addr.ether);
212                 if (k < 0)
213                         r = k;
214         }
215         if (r < 0)
216                 link_enter_failed(link);
217 
218         return r;
219 }
220 
ipv4acd_start(Link * link)221 int ipv4acd_start(Link *link) {
222         Address *address;
223         int r;
224 
225         assert(link);
226 
227         SET_FOREACH(address, link->addresses) {
228                 if (!address->acd)
229                         continue;
230 
231                 if (sd_ipv4acd_is_running(address->acd))
232                         continue;
233 
234                 r = sd_ipv4acd_start(address->acd, true);
235                 if (r < 0)
236                         return r;
237         }
238 
239         return 0;
240 }
241 
ipv4acd_stop(Link * link)242 int ipv4acd_stop(Link *link) {
243         Address *address;
244         int k, r = 0;
245 
246         assert(link);
247 
248         SET_FOREACH(address, link->addresses) {
249                 if (!address->acd)
250                         continue;
251 
252                 k = sd_ipv4acd_stop(address->acd);
253                 if (k < 0)
254                         r = k;
255         }
256 
257         return r;
258 }
259 
ipv4acd_set_ifname(Link * link)260 int ipv4acd_set_ifname(Link *link) {
261         Address *address;
262         int r;
263 
264         assert(link);
265 
266         SET_FOREACH(address, link->addresses) {
267                 if (!address->acd)
268                         continue;
269 
270                 r = sd_ipv4acd_set_ifname(address->acd, link->ifname);
271                 if (r < 0)
272                         return r;
273         }
274 
275         return 0;
276 }
277