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