]>
Commit | Line | Data |
---|---|---|
f579559b TG |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2013 Tom Gundersen <teg@jklm.no> | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #pragma once | |
23 | ||
24 | #include <arpa/inet.h> | |
f579559b TG |
25 | |
26 | #include "sd-event.h" | |
27 | #include "sd-rtnl.h" | |
1346b1f0 | 28 | #include "sd-bus.h" |
f5be5601 | 29 | #include "sd-dhcp-client.h" |
5c1d3fc9 | 30 | #include "sd-ipv4ll.h" |
f579559b TG |
31 | #include "udev.h" |
32 | ||
33 | #include "rtnl-util.h" | |
34 | #include "hashmap.h" | |
35 | #include "list.h" | |
06f021a8 | 36 | #include "set.h" |
2cc412b5 | 37 | #include "condition-util.h" |
f579559b | 38 | |
aba496a5 UTL |
39 | #define CACHE_INFO_INFINITY_LIFE_TIME 0xFFFFFFFFU |
40 | ||
1a436809 | 41 | typedef struct NetDev NetDev; |
f579559b TG |
42 | typedef struct Network Network; |
43 | typedef struct Link Link; | |
44 | typedef struct Address Address; | |
45 | typedef struct Route Route; | |
46 | typedef struct Manager Manager; | |
47 | ||
52433f6b | 48 | typedef struct netdev_enslave_callback netdev_enslave_callback; |
02b59d57 | 49 | |
52433f6b | 50 | struct netdev_enslave_callback { |
02b59d57 TG |
51 | sd_rtnl_message_handler_t callback; |
52 | Link *link; | |
53 | ||
52433f6b | 54 | LIST_FIELDS(netdev_enslave_callback, callbacks); |
02b59d57 TG |
55 | }; |
56 | ||
fe6b2d55 TG |
57 | typedef enum MacVlanMode { |
58 | NETDEV_MACVLAN_MODE_PRIVATE = MACVLAN_MODE_PRIVATE, | |
59 | NETDEV_MACVLAN_MODE_VEPA = MACVLAN_MODE_VEPA, | |
60 | NETDEV_MACVLAN_MODE_BRIDGE = MACVLAN_MODE_BRIDGE, | |
61 | NETDEV_MACVLAN_MODE_PASSTHRU = MACVLAN_MODE_PASSTHRU, | |
62 | _NETDEV_MACVLAN_MODE_MAX, | |
63 | _NETDEV_MACVLAN_MODE_INVALID = -1 | |
64 | } MacVlanMode; | |
65 | ||
1a436809 | 66 | typedef enum NetDevKind { |
52433f6b TG |
67 | NETDEV_KIND_BRIDGE, |
68 | NETDEV_KIND_BOND, | |
54abf461 | 69 | NETDEV_KIND_VLAN, |
fe6b2d55 | 70 | NETDEV_KIND_MACVLAN, |
7951dea2 SS |
71 | NETDEV_KIND_IPIP, |
72 | NETDEV_KIND_GRE, | |
73 | NETDEV_KIND_SIT, | |
52433f6b TG |
74 | _NETDEV_KIND_MAX, |
75 | _NETDEV_KIND_INVALID = -1 | |
1a436809 | 76 | } NetDevKind; |
52433f6b | 77 | |
1a436809 | 78 | typedef enum NetDevState { |
52433f6b TG |
79 | NETDEV_STATE_FAILED, |
80 | NETDEV_STATE_CREATING, | |
81 | NETDEV_STATE_READY, | |
2cc7e981 | 82 | NETDEV_STATE_LINGER, |
52433f6b TG |
83 | _NETDEV_STATE_MAX, |
84 | _NETDEV_STATE_INVALID = -1, | |
1a436809 | 85 | } NetDevState; |
52433f6b | 86 | |
1a436809 | 87 | struct NetDev { |
02b59d57 TG |
88 | Manager *manager; |
89 | ||
14b746f7 TG |
90 | int n_ref; |
91 | ||
02b59d57 TG |
92 | char *filename; |
93 | ||
edbb03e9 TG |
94 | Condition *match_host; |
95 | Condition *match_virt; | |
96 | Condition *match_kernel; | |
97 | Condition *match_arch; | |
c0dda186 | 98 | |
02b59d57 | 99 | char *description; |
af4e9e2c | 100 | char *ifname; |
7951dea2 | 101 | size_t mtu; |
1a436809 | 102 | NetDevKind kind; |
02b59d57 | 103 | |
672682a6 | 104 | uint64_t vlanid; |
fe6b2d55 | 105 | int32_t macvlan_mode; |
54abf461 | 106 | |
50add290 | 107 | int ifindex; |
1a436809 | 108 | NetDevState state; |
02b59d57 | 109 | |
7951dea2 SS |
110 | unsigned tunnel_ttl; |
111 | unsigned tunnel_tos; | |
112 | struct in_addr tunnel_local; | |
113 | struct in_addr tunnel_remote; | |
114 | ||
52433f6b | 115 | LIST_HEAD(netdev_enslave_callback, callbacks); |
02b59d57 TG |
116 | }; |
117 | ||
f579559b TG |
118 | struct Network { |
119 | Manager *manager; | |
120 | ||
121 | char *filename; | |
122 | ||
123 | struct ether_addr *match_mac; | |
124 | char *match_path; | |
125 | char *match_driver; | |
126 | char *match_type; | |
127 | char *match_name; | |
2cc412b5 TG |
128 | Condition *match_host; |
129 | Condition *match_virt; | |
130 | Condition *match_kernel; | |
edbb03e9 | 131 | Condition *match_arch; |
f579559b TG |
132 | |
133 | char *description; | |
1a436809 TG |
134 | NetDev *bridge; |
135 | NetDev *bond; | |
7951dea2 | 136 | NetDev *tunnel; |
672682a6 | 137 | Hashmap *vlans; |
fe6b2d55 | 138 | Hashmap *macvlans; |
f5be5601 | 139 | bool dhcp; |
5be4d38e | 140 | bool dhcp_dns; |
bcb7a07e | 141 | bool dhcp_ntp; |
4f882b2a | 142 | bool dhcp_mtu; |
1346b1f0 | 143 | bool dhcp_hostname; |
039ebe6a | 144 | bool dhcp_domainname; |
eb27aeca | 145 | bool dhcp_critical; |
5c1d3fc9 | 146 | bool ipv4ll; |
f579559b | 147 | |
f048a16b TG |
148 | LIST_HEAD(Address, static_addresses); |
149 | LIST_HEAD(Route, static_routes); | |
f579559b | 150 | |
6ae115c1 TG |
151 | Hashmap *addresses_by_section; |
152 | Hashmap *routes_by_section; | |
153 | ||
d4920165 | 154 | LIST_HEAD(Address, dns); |
bcb7a07e | 155 | LIST_HEAD(Address, ntp); |
06f021a8 | 156 | |
f579559b TG |
157 | LIST_FIELDS(Network, networks); |
158 | }; | |
159 | ||
160 | struct Address { | |
161 | Network *network; | |
6ae115c1 | 162 | uint64_t section; |
f579559b TG |
163 | |
164 | unsigned char family; | |
165 | unsigned char prefixlen; | |
5c1d3fc9 | 166 | unsigned char scope; |
f579559b TG |
167 | char *label; |
168 | ||
eb0ea358 | 169 | struct in_addr broadcast; |
aba496a5 | 170 | struct ifa_cacheinfo cinfo; |
8cd11a0f | 171 | |
f579559b TG |
172 | union { |
173 | struct in_addr in; | |
174 | struct in6_addr in6; | |
175 | } in_addr; | |
176 | ||
3d3d4255 | 177 | LIST_FIELDS(Address, addresses); |
f579559b TG |
178 | }; |
179 | ||
180 | struct Route { | |
181 | Network *network; | |
6ae115c1 | 182 | uint64_t section; |
f579559b TG |
183 | |
184 | unsigned char family; | |
6ae115c1 | 185 | unsigned char dst_prefixlen; |
5c1d3fc9 UTL |
186 | unsigned char scope; |
187 | uint32_t metrics; | |
f579559b TG |
188 | |
189 | union { | |
190 | struct in_addr in; | |
191 | struct in6_addr in6; | |
192 | } in_addr; | |
193 | ||
6ae115c1 TG |
194 | union { |
195 | struct in_addr in; | |
196 | struct in6_addr in6; | |
197 | } dst_addr; | |
198 | ||
3d3d4255 | 199 | LIST_FIELDS(Route, routes); |
f579559b TG |
200 | }; |
201 | ||
f882c247 | 202 | typedef enum LinkState { |
505f8da7 | 203 | LINK_STATE_INITIALIZING, |
52433f6b | 204 | LINK_STATE_ENSLAVING, |
ef1ba606 TG |
205 | LINK_STATE_SETTING_ADDRESSES, |
206 | LINK_STATE_SETTING_ROUTES, | |
f882c247 | 207 | LINK_STATE_CONFIGURED, |
57bd6899 | 208 | LINK_STATE_UNMANAGED, |
f882c247 | 209 | LINK_STATE_FAILED, |
370e9930 | 210 | LINK_STATE_LINGER, |
f882c247 TG |
211 | _LINK_STATE_MAX, |
212 | _LINK_STATE_INVALID = -1 | |
213 | } LinkState; | |
214 | ||
f579559b TG |
215 | struct Link { |
216 | Manager *manager; | |
217 | ||
14b746f7 TG |
218 | int n_ref; |
219 | ||
0617ffab | 220 | uint64_t ifindex; |
c166a070 | 221 | char *ifname; |
fe8db0c5 | 222 | char *state_file; |
8cd11a0f | 223 | struct ether_addr mac; |
b5db00e5 | 224 | struct udev_device *udev_device; |
f579559b TG |
225 | |
226 | unsigned flags; | |
1e9be60b | 227 | uint8_t operstate; |
f579559b TG |
228 | |
229 | Network *network; | |
f882c247 TG |
230 | |
231 | LinkState state; | |
232 | ||
f5be5601 TG |
233 | unsigned addr_messages; |
234 | unsigned route_messages; | |
52433f6b | 235 | unsigned enslaving; |
f5be5601 | 236 | |
428fd0a7 TG |
237 | LIST_HEAD(Address, addresses); |
238 | ||
a6cc569e TG |
239 | sd_dhcp_client *dhcp_client; |
240 | sd_dhcp_lease *dhcp_lease; | |
68a8723c | 241 | char *lease_file; |
a6cc569e | 242 | uint16_t original_mtu; |
5c1d3fc9 | 243 | sd_ipv4ll *ipv4ll; |
f579559b TG |
244 | }; |
245 | ||
246 | struct Manager { | |
247 | sd_rtnl *rtnl; | |
248 | sd_event *event; | |
1346b1f0 | 249 | sd_bus *bus; |
f579559b TG |
250 | struct udev *udev; |
251 | struct udev_monitor *udev_monitor; | |
252 | sd_event_source *udev_event_source; | |
0c2f9b84 TG |
253 | sd_event_source *sigterm_event_source; |
254 | sd_event_source *sigint_event_source; | |
f579559b | 255 | |
bbf7c048 TG |
256 | char *state_file; |
257 | ||
f579559b | 258 | Hashmap *links; |
52433f6b | 259 | Hashmap *netdevs; |
f579559b TG |
260 | LIST_HEAD(Network, networks); |
261 | ||
f579559b | 262 | usec_t network_dirs_ts_usec; |
7951dea2 | 263 | struct kmod_ctx *kmod_ctx; |
f579559b TG |
264 | }; |
265 | ||
2ad8416d ZJS |
266 | extern const char* const network_dirs[]; |
267 | ||
f579559b TG |
268 | /* Manager */ |
269 | ||
270 | int manager_new(Manager **ret); | |
271 | void manager_free(Manager *m); | |
272 | ||
02b59d57 TG |
273 | int manager_load_config(Manager *m); |
274 | bool manager_should_reload(Manager *m); | |
275 | ||
505f8da7 | 276 | int manager_rtnl_enumerate_links(Manager *m); |
f579559b | 277 | |
f882c247 | 278 | int manager_rtnl_listen(Manager *m); |
505f8da7 | 279 | int manager_udev_listen(Manager *m); |
1346b1f0 | 280 | int manager_bus_listen(Manager *m); |
f882c247 | 281 | |
bbf7c048 | 282 | int manager_save(Manager *m); |
3bef724f | 283 | |
f579559b TG |
284 | DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); |
285 | #define _cleanup_manager_free_ _cleanup_(manager_freep) | |
286 | ||
1a436809 | 287 | /* NetDev */ |
52433f6b TG |
288 | |
289 | int netdev_load(Manager *manager); | |
2cc7e981 | 290 | void netdev_drop(NetDev *netdev); |
02b59d57 | 291 | |
14b746f7 TG |
292 | NetDev *netdev_unref(NetDev *netdev); |
293 | NetDev *netdev_ref(NetDev *netdev); | |
02b59d57 | 294 | |
14b746f7 TG |
295 | DEFINE_TRIVIAL_CLEANUP_FUNC(NetDev*, netdev_unref); |
296 | #define _cleanup_netdev_unref_ _cleanup_(netdev_unrefp) | |
02b59d57 | 297 | |
1a436809 | 298 | int netdev_get(Manager *manager, const char *name, NetDev **ret); |
d39edfc7 | 299 | int netdev_set_ifindex(NetDev *netdev, sd_rtnl_message *newlink); |
1a436809 | 300 | int netdev_enslave(NetDev *netdev, Link *link, sd_rtnl_message_handler_t cb); |
7951dea2 | 301 | int netdev_create_tunnel(Link *link, sd_rtnl_message_handler_t callback); |
02b59d57 | 302 | |
1a436809 TG |
303 | const char *netdev_kind_to_string(NetDevKind d) _const_; |
304 | NetDevKind netdev_kind_from_string(const char *d) _pure_; | |
52433f6b | 305 | |
fe6b2d55 TG |
306 | const char *macvlan_mode_to_string(MacVlanMode d) _const_; |
307 | MacVlanMode macvlan_mode_from_string(const char *d) _pure_; | |
308 | ||
52433f6b | 309 | int config_parse_netdev_kind(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); |
02b59d57 | 310 | |
fe6b2d55 TG |
311 | int config_parse_macvlan_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); |
312 | ||
c0dda186 TG |
313 | /* gperf */ |
314 | const struct ConfigPerfItem* network_netdev_gperf_lookup(const char *key, unsigned length); | |
315 | ||
f579559b TG |
316 | /* Network */ |
317 | ||
318 | int network_load(Manager *manager); | |
f579559b TG |
319 | |
320 | void network_free(Network *network); | |
321 | ||
322 | DEFINE_TRIVIAL_CLEANUP_FUNC(Network*, network_free); | |
323 | #define _cleanup_network_free_ _cleanup_(network_freep) | |
324 | ||
505f8da7 TG |
325 | int network_get(Manager *manager, struct udev_device *device, |
326 | const char *ifname, const struct ether_addr *mac, | |
327 | Network **ret); | |
f579559b TG |
328 | int network_apply(Manager *manager, Network *network, Link *link); |
329 | ||
69a93e7d | 330 | int config_parse_netdev(const char *unit, const char *filename, unsigned line, |
02b59d57 TG |
331 | const char *section, unsigned section_line, const char *lvalue, |
332 | int ltype, const char *rvalue, void *data, void *userdata); | |
333 | ||
7951dea2 SS |
334 | int config_parse_tunnel(const char *unit, |
335 | const char *filename, | |
336 | unsigned line, | |
337 | const char *section, | |
338 | unsigned section_line, | |
339 | const char *lvalue, | |
340 | int ltype, | |
341 | const char *rvalue, | |
342 | void *data, | |
343 | void *userdata); | |
344 | ||
345 | int config_parse_tunnel_address(const char *unit, | |
346 | const char *filename, | |
347 | unsigned line, | |
348 | const char *section, | |
349 | unsigned section_line, | |
350 | const char *lvalue, | |
351 | int ltype, | |
352 | const char *rvalue, | |
353 | void *data, | |
354 | void *userdata); | |
355 | ||
02b59d57 | 356 | /* gperf */ |
c0dda186 | 357 | const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, unsigned length); |
f579559b TG |
358 | |
359 | /* Route */ | |
f048a16b TG |
360 | int route_new_static(Network *network, unsigned section, Route **ret); |
361 | int route_new_dynamic(Route **ret); | |
f579559b | 362 | void route_free(Route *route); |
f882c247 | 363 | int route_configure(Route *route, Link *link, sd_rtnl_message_handler_t callback); |
5c1d3fc9 UTL |
364 | int route_drop(Route *route, Link *link, sd_rtnl_message_handler_t callback); |
365 | ||
f579559b TG |
366 | |
367 | DEFINE_TRIVIAL_CLEANUP_FUNC(Route*, route_free); | |
368 | #define _cleanup_route_free_ _cleanup_(route_freep) | |
369 | ||
370 | int config_parse_gateway(const char *unit, const char *filename, unsigned line, | |
71a61510 TG |
371 | const char *section, unsigned section_line, const char *lvalue, |
372 | int ltype, const char *rvalue, void *data, void *userdata); | |
f579559b | 373 | |
6ae115c1 TG |
374 | int config_parse_destination(const char *unit, const char *filename, unsigned line, |
375 | const char *section, unsigned section_line, const char *lvalue, | |
376 | int ltype, const char *rvalue, void *data, void *userdata); | |
377 | ||
f579559b | 378 | /* Address */ |
f048a16b TG |
379 | int address_new_static(Network *network, unsigned section, Address **ret); |
380 | int address_new_dynamic(Address **ret); | |
f579559b | 381 | void address_free(Address *address); |
f882c247 | 382 | int address_configure(Address *address, Link *link, sd_rtnl_message_handler_t callback); |
aba496a5 | 383 | int address_update(Address *address, Link *link, sd_rtnl_message_handler_t callback); |
407fe036 | 384 | int address_drop(Address *address, Link *link, sd_rtnl_message_handler_t callback); |
9505d3c6 | 385 | bool address_equal(Address *a1, Address *a2); |
f579559b TG |
386 | |
387 | DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free); | |
388 | #define _cleanup_address_free_ _cleanup_(address_freep) | |
389 | ||
3bef724f TG |
390 | int config_parse_dns(const char *unit, const char *filename, unsigned line, |
391 | const char *section, unsigned section_line, const char *lvalue, | |
392 | int ltype, const char *rvalue, void *data, void *userdata); | |
393 | ||
f579559b | 394 | int config_parse_address(const char *unit, const char *filename, unsigned line, |
71a61510 TG |
395 | const char *section, unsigned section_line, const char *lvalue, |
396 | int ltype, const char *rvalue, void *data, void *userdata); | |
f579559b | 397 | |
eb0ea358 TG |
398 | int config_parse_broadcast(const char *unit, const char *filename, unsigned line, |
399 | const char *section, unsigned section_line, const char *lvalue, | |
400 | int ltype, const char *rvalue, void *data, void *userdata); | |
401 | ||
6ae115c1 TG |
402 | int config_parse_label(const char *unit, const char *filename, unsigned line, |
403 | const char *section, unsigned section_line, const char *lvalue, | |
404 | int ltype, const char *rvalue, void *data, void *userdata); | |
405 | ||
f579559b TG |
406 | /* Link */ |
407 | ||
14b746f7 TG |
408 | Link *link_unref(Link *link); |
409 | Link *link_ref(Link *link); | |
11a7f229 | 410 | int link_get(Manager *m, int ifindex, Link **ret); |
505f8da7 | 411 | int link_add(Manager *manager, sd_rtnl_message *message, Link **ret); |
370e9930 | 412 | void link_drop(Link *link); |
f579559b | 413 | |
22936833 | 414 | int link_update(Link *link, sd_rtnl_message *message); |
fbbeb65a | 415 | int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *userdata); |
dd3efc09 | 416 | |
505f8da7 TG |
417 | int link_initialized(Link *link, struct udev_device *device); |
418 | ||
fe8db0c5 TG |
419 | int link_save(Link *link); |
420 | ||
bbf7c048 TG |
421 | bool link_has_carrier(unsigned flags, uint8_t operstate); |
422 | ||
fe8db0c5 TG |
423 | const char* link_state_to_string(LinkState s) _const_; |
424 | LinkState link_state_from_string(const char *s) _pure_; | |
425 | ||
14b746f7 TG |
426 | DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_unref); |
427 | #define _cleanup_link_unref_ _cleanup_(link_unrefp) | |
3333d748 ZJS |
428 | |
429 | /* Macros which append INTERFACE= to the message */ | |
430 | ||
987efa17 | 431 | #define log_full_link(level, link, fmt, ...) log_meta_object(level, __FILE__, __LINE__, __func__, "INTERFACE=", link->ifname, "%*s: " fmt, IFNAMSIZ, link->ifname, ##__VA_ARGS__) |
39032b87 ZJS |
432 | #define log_debug_link(link, ...) log_full_link(LOG_DEBUG, link, ##__VA_ARGS__) |
433 | #define log_info_link(link, ...) log_full_link(LOG_INFO, link, ##__VA_ARGS__) | |
434 | #define log_notice_link(link, ...) log_full_link(LOG_NOTICE, link, ##__VA_ARGS__) | |
435 | #define log_warning_link(link, ...) log_full_link(LOG_WARNING, link, ##__VA_ARGS__) | |
436 | #define log_error_link(link, ...) log_full_link(LOG_ERR, link, ##__VA_ARGS__) | |
3333d748 ZJS |
437 | |
438 | #define log_struct_link(level, link, ...) log_struct(level, "INTERFACE=%s", link->ifname, __VA_ARGS__) | |
439 | ||
440 | /* More macros which append INTERFACE= to the message */ | |
441 | ||
987efa17 | 442 | #define log_full_netdev(level, netdev, fmt, ...) log_meta_object(level, __FILE__, __LINE__, __func__, "INTERFACE=", netdev->ifname, "%*s: " fmt, IFNAMSIZ, netdev->ifname, ##__VA_ARGS__) |
52433f6b TG |
443 | #define log_debug_netdev(netdev, ...) log_full_netdev(LOG_DEBUG, netdev, ##__VA_ARGS__) |
444 | #define log_info_netdev(netdev, ...) log_full_netdev(LOG_INFO, netdev, ##__VA_ARGS__) | |
445 | #define log_notice_netdev(netdev, ...) log_full_netdev(LOG_NOTICE, netdev, ##__VA_ARGS__) | |
446 | #define log_warning_netdev(netdev, ...) log_full_netdev(LOG_WARNING, netdev,## __VA_ARGS__) | |
447 | #define log_error_netdev(netdev, ...) log_full_netdev(LOG_ERR, netdev, ##__VA_ARGS__) | |
3333d748 | 448 | |
af4e9e2c | 449 | #define log_struct_netdev(level, netdev, ...) log_struct(level, "INTERFACE=%s", netdev->ifname, __VA_ARGS__) |
3333d748 | 450 | |
af4e9e2c | 451 | #define NETDEV(netdev) "INTERFACE=%s", netdev->ifname |
62870613 ZJS |
452 | #define ADDRESS_FMT_VAL(address) \ |
453 | (address).s_addr & 0xFF, \ | |
454 | ((address).s_addr >> 8) & 0xFF, \ | |
455 | ((address).s_addr >> 16) & 0xFF, \ | |
456 | (address).s_addr >> 24 |