]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-ipv4acd.c
network: introduce per-interface IP forwarding settings
[thirdparty/systemd.git] / src / network / networkd-ipv4acd.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <net/if.h> /* IFF_LOOPBACK */
4 #include <net/if_arp.h> /* ARPHRD_ETHER */
5
6 #include "sd-dhcp-client.h"
7 #include "sd-ipv4acd.h"
8
9 #include "ipvlan.h"
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
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
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
48 static bool address_ipv4acd_enabled(Link *link, const Address *address) {
49 assert(link);
50 assert(address);
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
62 return link_ipv4acd_supported(link);
63 }
64
65 bool ipv4acd_bound(Link *link, const Address *address) {
66 sd_ipv4acd *acd;
67
68 assert(link);
69 assert(address);
70
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)
76 return true;
77
78 return sd_ipv4acd_is_bound(acd) > 0;
79 }
80
81 static int static_ipv4acd_address_remove(Link *link, Address *address, bool on_conflict) {
82 int r;
83
84 assert(link);
85 assert(address);
86
87 if (!address_exists(address))
88 return 0; /* Not assigned. */
89
90 if (on_conflict)
91 log_link_warning(link, "Dropping address %s, as an address conflict was detected.", IN4_ADDR_TO_STRING(&address->in_addr.in));
92 else
93 log_link_debug(link, "Removing address %s, as the ACD client is stopped.", IN4_ADDR_TO_STRING(&address->in_addr.in));
94
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. */
97 r = address_remove(address, link);
98 if (r < 0)
99 return log_link_warning_errno(link, r, "Failed to remove address %s: %m", IN4_ADDR_TO_STRING(&address->in_addr.in));
100
101 return 0;
102 }
103
104 static int dhcp4_address_on_conflict(Link *link) {
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
118 log_link_warning(link, "Dropping DHCPv4 lease, as an address conflict was detected.");
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
127 static void on_acd(sd_ipv4acd *acd, int event, void *userdata) {
128 Link *link = ASSERT_PTR(userdata);
129 Address *address = NULL;
130 struct in_addr a;
131 int r;
132
133 assert(acd);
134
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);
142
143 switch (event) {
144 case SD_IPV4ACD_EVENT_STOP:
145 if (!address)
146 break;
147
148 if (address->source == NETWORK_CONFIG_SOURCE_STATIC) {
149 r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ false);
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:
159 log_link_debug(link, "Successfully claimed address %s", IN4_ADDR_TO_STRING(&a));
160 break;
161
162 case SD_IPV4ACD_EVENT_CONFLICT:
163 if (!address)
164 break;
165
166 log_link_warning(link, "Dropping address %s, as an address conflict was detected.", IN4_ADDR_TO_STRING(&a));
167
168 if (address->source == NETWORK_CONFIG_SOURCE_STATIC)
169 r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ true);
170 else
171 r = dhcp4_address_on_conflict(link);
172 if (r < 0)
173 link_enter_failed(link);
174 break;
175
176 default:
177 assert_not_reached();
178 }
179 }
180
181 static int ipv4acd_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) {
182 Manager *m = ASSERT_PTR(userdata);
183 struct hw_addr_data hw_addr;
184
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
195 static int ipv4acd_start_one(Link *link, sd_ipv4acd *acd) {
196 assert(link);
197 assert(acd);
198
199 if (sd_ipv4acd_is_running(acd))
200 return 0;
201
202 if (!link_has_carrier(link))
203 return 0;
204
205 return sd_ipv4acd_start(acd, /* reset_conflicts = */ true);
206 }
207
208 int ipv4acd_configure(Link *link, const Address *address) {
209 _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
210 sd_ipv4acd *existing;
211 int r;
212
213 assert(link);
214 assert(link->manager);
215 assert(address);
216
217 if (address->family != AF_INET)
218 return 0;
219
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);
227
228 log_link_debug(link, "Configuring IPv4ACD for address %s.", IN4_ADDR_TO_STRING(&address->in_addr.in));
229
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);
235 if (r < 0)
236 return r;
237
238 r = sd_ipv4acd_set_ifindex(acd, link->ifindex);
239 if (r < 0)
240 return r;
241
242 r = sd_ipv4acd_set_mac(acd, &link->hw_addr.ether);
243 if (r < 0)
244 return r;
245
246 r = sd_ipv4acd_set_address(acd, &address->in_addr.in);
247 if (r < 0)
248 return r;
249
250 r = sd_ipv4acd_set_callback(acd, on_acd, link);
251 if (r < 0)
252 return r;
253
254 r = sd_ipv4acd_set_check_mac_callback(acd, ipv4acd_check_mac, link->manager);
255 if (r < 0)
256 return r;
257
258 r = hashmap_ensure_put(&link->ipv4acd_by_address, &ipv4acd_hash_ops, IN4_ADDR_TO_PTR(&address->in_addr.in), acd);
259 if (r < 0)
260 return r;
261
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)));
273 }
274
275 int ipv4acd_update_mac(Link *link) {
276 sd_ipv4acd *acd;
277 int r;
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
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;
290 }
291
292 return 0;
293 }
294
295 int ipv4acd_start(Link *link) {
296 sd_ipv4acd *acd;
297 int r;
298
299 assert(link);
300
301 HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
302 r = ipv4acd_start_one(link, acd);
303 if (r < 0)
304 return r;
305 }
306
307 return 0;
308 }
309
310 int ipv4acd_stop(Link *link) {
311 sd_ipv4acd *acd;
312 int k, r = 0;
313
314 assert(link);
315
316 HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
317 k = sd_ipv4acd_stop(acd);
318 if (k < 0)
319 r = k;
320 }
321
322 return r;
323 }
324
325 int ipv4acd_set_ifname(Link *link) {
326 sd_ipv4acd *acd;
327 int r;
328
329 assert(link);
330
331 HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
332 r = sd_ipv4acd_set_ifname(acd, link->ifname);
333 if (r < 0)
334 return r;
335 }
336
337 return 0;
338 }