]>
Commit | Line | Data |
---|---|---|
76a86ffd YW |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
239e4a42 KR |
3 | #include <net/if.h> /* IFF_LOOPBACK */ |
4 | #include <net/if_arp.h> /* ARPHRD_ETHER */ | |
29104ded | 5 | |
76a86ffd YW |
6 | #include "sd-dhcp-client.h" |
7 | #include "sd-ipv4acd.h" | |
8 | ||
29104ded | 9 | #include "ipvlan.h" |
76a86ffd YW |
10 | #include "networkd-address.h" |
11 | #include "networkd-dhcp4.h" | |
12 | #include "networkd-ipv4acd.h" | |
13 | #include "networkd-link.h" | |
14 | #include "networkd-manager.h" | |
15 | ||
5385e5f9 YW |
16 | DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( |
17 | ipv4acd_hash_ops, | |
18 | void, trivial_hash_func, trivial_compare_func, | |
19 | sd_ipv4acd, sd_ipv4acd_unref); | |
20 | ||
29104ded YW |
21 | bool link_ipv4acd_supported(Link *link) { |
22 | assert(link); | |
23 | ||
24 | if (link->flags & IFF_LOOPBACK) | |
25 | return false; | |
26 | ||
27 | /* ARPHRD_INFINIBAND seems to potentially support IPv4ACD. | |
28 | * But currently sd-ipv4acd only supports ARPHRD_ETHER. */ | |
29 | if (link->iftype != ARPHRD_ETHER) | |
30 | return false; | |
31 | ||
32 | if (link->hw_addr.length != ETH_ALEN) | |
33 | return false; | |
34 | ||
35 | if (ether_addr_is_null(&link->hw_addr.ether)) | |
36 | return false; | |
37 | ||
38 | if (streq_ptr(link->kind, "vrf")) | |
39 | return false; | |
40 | ||
41 | /* L3 or L3S mode do not support ARP. */ | |
42 | if (IN_SET(link_get_ipvlan_mode(link), NETDEV_IPVLAN_MODE_L3, NETDEV_IPVLAN_MODE_L3S)) | |
43 | return false; | |
44 | ||
45 | return true; | |
46 | } | |
47 | ||
5385e5f9 YW |
48 | static bool address_ipv4acd_enabled(Link *link, const Address *address) { |
49 | assert(link); | |
03ff3c5a | 50 | assert(address); |
03ff3c5a YW |
51 | |
52 | if (address->family != AF_INET) | |
53 | return false; | |
54 | ||
55 | if (!FLAGS_SET(address->duplicate_address_detection, ADDRESS_FAMILY_IPV4)) | |
56 | return false; | |
57 | ||
58 | /* Currently, only static and DHCP4 addresses are supported. */ | |
59 | if (!IN_SET(address->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_DHCP4)) | |
60 | return false; | |
61 | ||
5385e5f9 | 62 | return link_ipv4acd_supported(link); |
03ff3c5a YW |
63 | } |
64 | ||
5385e5f9 YW |
65 | bool ipv4acd_bound(Link *link, const Address *address) { |
66 | sd_ipv4acd *acd; | |
67 | ||
68 | assert(link); | |
e402e99e YW |
69 | assert(address); |
70 | ||
5385e5f9 YW |
71 | if (address->family != AF_INET) |
72 | return true; | |
73 | ||
74 | acd = hashmap_get(link->ipv4acd_by_address, IN4_ADDR_TO_PTR(&address->in_addr.in)); | |
75 | if (!acd) | |
e402e99e YW |
76 | return true; |
77 | ||
5385e5f9 | 78 | return sd_ipv4acd_is_bound(acd) > 0; |
e402e99e YW |
79 | } |
80 | ||
3b6a3bde | 81 | static int static_ipv4acd_address_remove(Link *link, Address *address, bool on_conflict) { |
76a86ffd YW |
82 | int r; |
83 | ||
84 | assert(link); | |
85 | assert(address); | |
86 | ||
3b6a3bde YW |
87 | if (!address_exists(address)) |
88 | return 0; /* Not assigned. */ | |
76a86ffd | 89 | |
3b6a3bde | 90 | if (on_conflict) |
262ac7fc | 91 | log_link_warning(link, "Dropping address %s, as an address conflict was detected.", IN4_ADDR_TO_STRING(&address->in_addr.in)); |
3b6a3bde | 92 | else |
262ac7fc | 93 | log_link_debug(link, "Removing address %s, as the ACD client is stopped.", IN4_ADDR_TO_STRING(&address->in_addr.in)); |
76a86ffd | 94 | |
f22b586a YW |
95 | /* Do not call address_remove_and_cancel() here. Otherwise, the request is cancelled, and the |
96 | * interface may be in configured state without the address. */ | |
7f74b00a | 97 | r = address_remove(address, link); |
76a86ffd | 98 | if (r < 0) |
262ac7fc | 99 | return log_link_warning_errno(link, r, "Failed to remove address %s: %m", IN4_ADDR_TO_STRING(&address->in_addr.in)); |
76a86ffd YW |
100 | |
101 | return 0; | |
102 | } | |
103 | ||
722ac246 | 104 | static int dhcp4_address_on_conflict(Link *link) { |
76a86ffd YW |
105 | int r; |
106 | ||
107 | assert(link); | |
108 | assert(link->dhcp_client); | |
109 | ||
110 | r = sd_dhcp_client_send_decline(link->dhcp_client); | |
111 | if (r < 0) | |
112 | log_link_warning_errno(link, r, "Failed to send DHCP DECLINE, ignoring: %m"); | |
113 | ||
114 | if (!link->dhcp_lease) | |
115 | /* Unlikely, but during probing the address, the lease may be lost. */ | |
116 | return 0; | |
117 | ||
cd3a828c | 118 | log_link_warning(link, "Dropping DHCPv4 lease, as an address conflict was detected."); |
76a86ffd YW |
119 | r = dhcp4_lease_lost(link); |
120 | if (r < 0) | |
121 | return log_link_warning_errno(link, r, "Failed to drop DHCPv4 lease: %m"); | |
122 | ||
123 | /* It is not necessary to call address_remove() here, as dhcp4_lease_lost() removes it. */ | |
124 | return 0; | |
125 | } | |
126 | ||
3b6a3bde | 127 | static void on_acd(sd_ipv4acd *acd, int event, void *userdata) { |
5385e5f9 YW |
128 | Link *link = ASSERT_PTR(userdata); |
129 | Address *address = NULL; | |
262ac7fc | 130 | struct in_addr a; |
76a86ffd YW |
131 | int r; |
132 | ||
133 | assert(acd); | |
76a86ffd | 134 | |
5385e5f9 YW |
135 | r = sd_ipv4acd_get_address(acd, &a); |
136 | if (r < 0) { | |
137 | log_link_warning_errno(link, r, "Failed to get address from IPv4ACD: %m"); | |
138 | link_enter_failed(link); | |
139 | } | |
140 | ||
141 | (void) link_get_ipv4_address(link, &a, 0, &address); | |
76a86ffd YW |
142 | |
143 | switch (event) { | |
144 | case SD_IPV4ACD_EVENT_STOP: | |
5385e5f9 YW |
145 | if (!address) |
146 | break; | |
e402e99e | 147 | |
3b6a3bde YW |
148 | if (address->source == NETWORK_CONFIG_SOURCE_STATIC) { |
149 | r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ false); | |
76a86ffd YW |
150 | if (r < 0) |
151 | link_enter_failed(link); | |
152 | } | |
153 | ||
154 | /* We have nothing to do for DHCPv4 lease here, as the dhcp client is already stopped | |
155 | * when stopping the ipv4acd client. See link_stop_engines(). */ | |
156 | break; | |
157 | ||
158 | case SD_IPV4ACD_EVENT_BIND: | |
262ac7fc | 159 | log_link_debug(link, "Successfully claimed address %s", IN4_ADDR_TO_STRING(&a)); |
76a86ffd YW |
160 | break; |
161 | ||
162 | case SD_IPV4ACD_EVENT_CONFLICT: | |
5385e5f9 YW |
163 | if (!address) |
164 | break; | |
e402e99e | 165 | |
262ac7fc | 166 | log_link_warning(link, "Dropping address %s, as an address conflict was detected.", IN4_ADDR_TO_STRING(&a)); |
3b6a3bde YW |
167 | |
168 | if (address->source == NETWORK_CONFIG_SOURCE_STATIC) | |
169 | r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ true); | |
76a86ffd | 170 | else |
722ac246 | 171 | r = dhcp4_address_on_conflict(link); |
76a86ffd YW |
172 | if (r < 0) |
173 | link_enter_failed(link); | |
174 | break; | |
175 | ||
176 | default: | |
04499a70 | 177 | assert_not_reached(); |
76a86ffd YW |
178 | } |
179 | } | |
180 | ||
d7ab6ef0 | 181 | static int ipv4acd_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) { |
99534007 | 182 | Manager *m = ASSERT_PTR(userdata); |
d7ab6ef0 YW |
183 | struct hw_addr_data hw_addr; |
184 | ||
d7ab6ef0 YW |
185 | assert(mac); |
186 | ||
187 | hw_addr = (struct hw_addr_data) { | |
188 | .length = ETH_ALEN, | |
189 | .ether = *mac, | |
190 | }; | |
191 | ||
192 | return link_get_by_hw_addr(m, &hw_addr, NULL) >= 0; | |
193 | } | |
194 | ||
5385e5f9 YW |
195 | static int ipv4acd_start_one(Link *link, sd_ipv4acd *acd) { |
196 | assert(link); | |
197 | assert(acd); | |
e402e99e | 198 | |
5385e5f9 | 199 | if (sd_ipv4acd_is_running(acd)) |
e402e99e YW |
200 | return 0; |
201 | ||
5385e5f9 | 202 | if (!link_has_carrier(link)) |
e402e99e YW |
203 | return 0; |
204 | ||
5385e5f9 | 205 | return sd_ipv4acd_start(acd, /* reset_conflicts = */ true); |
e402e99e YW |
206 | } |
207 | ||
5385e5f9 YW |
208 | int ipv4acd_configure(Link *link, const Address *address) { |
209 | _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL; | |
210 | sd_ipv4acd *existing; | |
76a86ffd YW |
211 | int r; |
212 | ||
5385e5f9 YW |
213 | assert(link); |
214 | assert(link->manager); | |
3b6a3bde | 215 | assert(address); |
c565b655 | 216 | |
5385e5f9 | 217 | if (address->family != AF_INET) |
c565b655 | 218 | return 0; |
3b6a3bde | 219 | |
5385e5f9 YW |
220 | existing = hashmap_get(link->ipv4acd_by_address, IN4_ADDR_TO_PTR(&address->in_addr.in)); |
221 | ||
222 | if (!address_ipv4acd_enabled(link, address)) | |
223 | return sd_ipv4acd_stop(existing); | |
224 | ||
225 | if (existing) | |
226 | return ipv4acd_start_one(link, existing); | |
3b6a3bde | 227 | |
262ac7fc | 228 | log_link_debug(link, "Configuring IPv4ACD for address %s.", IN4_ADDR_TO_STRING(&address->in_addr.in)); |
76a86ffd | 229 | |
5385e5f9 YW |
230 | r = sd_ipv4acd_new(&acd); |
231 | if (r < 0) | |
232 | return r; | |
233 | ||
234 | r = sd_ipv4acd_attach_event(acd, link->manager->event, 0); | |
76a86ffd YW |
235 | if (r < 0) |
236 | return r; | |
237 | ||
5385e5f9 | 238 | r = sd_ipv4acd_set_ifindex(acd, link->ifindex); |
76a86ffd YW |
239 | if (r < 0) |
240 | return r; | |
241 | ||
5385e5f9 | 242 | r = sd_ipv4acd_set_mac(acd, &link->hw_addr.ether); |
76a86ffd YW |
243 | if (r < 0) |
244 | return r; | |
245 | ||
5385e5f9 | 246 | r = sd_ipv4acd_set_address(acd, &address->in_addr.in); |
76a86ffd YW |
247 | if (r < 0) |
248 | return r; | |
249 | ||
5385e5f9 | 250 | r = sd_ipv4acd_set_callback(acd, on_acd, link); |
76a86ffd YW |
251 | if (r < 0) |
252 | return r; | |
253 | ||
5385e5f9 | 254 | r = sd_ipv4acd_set_check_mac_callback(acd, ipv4acd_check_mac, link->manager); |
76a86ffd YW |
255 | if (r < 0) |
256 | return r; | |
257 | ||
5385e5f9 | 258 | r = hashmap_ensure_put(&link->ipv4acd_by_address, &ipv4acd_hash_ops, IN4_ADDR_TO_PTR(&address->in_addr.in), acd); |
d7ab6ef0 YW |
259 | if (r < 0) |
260 | return r; | |
261 | ||
5385e5f9 YW |
262 | return ipv4acd_start_one(link, TAKE_PTR(acd)); |
263 | } | |
264 | ||
265 | void ipv4acd_detach(Link *link, const Address *address) { | |
266 | assert(link); | |
267 | assert(address); | |
268 | ||
269 | if (address->family != AF_INET) | |
270 | return; | |
271 | ||
272 | sd_ipv4acd_unref(hashmap_remove(link->ipv4acd_by_address, IN4_ADDR_TO_PTR(&address->in_addr.in))); | |
76a86ffd YW |
273 | } |
274 | ||
76a86ffd | 275 | int ipv4acd_update_mac(Link *link) { |
5385e5f9 YW |
276 | sd_ipv4acd *acd; |
277 | int r; | |
76a86ffd YW |
278 | |
279 | assert(link); | |
280 | ||
281 | if (link->hw_addr.length != ETH_ALEN) | |
282 | return 0; | |
283 | if (ether_addr_is_null(&link->hw_addr.ether)) | |
284 | return 0; | |
285 | ||
5385e5f9 YW |
286 | HASHMAP_FOREACH(acd, link->ipv4acd_by_address) { |
287 | r = sd_ipv4acd_set_mac(acd, &link->hw_addr.ether); | |
288 | if (r < 0) | |
289 | return r; | |
76a86ffd | 290 | } |
76a86ffd | 291 | |
5385e5f9 | 292 | return 0; |
76a86ffd YW |
293 | } |
294 | ||
295 | int ipv4acd_start(Link *link) { | |
5385e5f9 | 296 | sd_ipv4acd *acd; |
76a86ffd YW |
297 | int r; |
298 | ||
299 | assert(link); | |
300 | ||
5385e5f9 YW |
301 | HASHMAP_FOREACH(acd, link->ipv4acd_by_address) { |
302 | r = ipv4acd_start_one(link, acd); | |
76a86ffd YW |
303 | if (r < 0) |
304 | return r; | |
305 | } | |
306 | ||
307 | return 0; | |
308 | } | |
309 | ||
310 | int ipv4acd_stop(Link *link) { | |
5385e5f9 | 311 | sd_ipv4acd *acd; |
76a86ffd YW |
312 | int k, r = 0; |
313 | ||
314 | assert(link); | |
315 | ||
5385e5f9 YW |
316 | HASHMAP_FOREACH(acd, link->ipv4acd_by_address) { |
317 | k = sd_ipv4acd_stop(acd); | |
76a86ffd YW |
318 | if (k < 0) |
319 | r = k; | |
320 | } | |
321 | ||
322 | return r; | |
323 | } | |
86173383 YW |
324 | |
325 | int ipv4acd_set_ifname(Link *link) { | |
5385e5f9 | 326 | sd_ipv4acd *acd; |
86173383 YW |
327 | int r; |
328 | ||
329 | assert(link); | |
330 | ||
5385e5f9 YW |
331 | HASHMAP_FOREACH(acd, link->ipv4acd_by_address) { |
332 | r = sd_ipv4acd_set_ifname(acd, link->ifname); | |
86173383 YW |
333 | if (r < 0) |
334 | return r; | |
335 | } | |
336 | ||
337 | return 0; | |
338 | } |