]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/network/networkd-address.c
networkd: port many log messages over to newer logging API
[thirdparty/systemd.git] / src / network / networkd-address.c
CommitLineData
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#include <net/if.h>
23
f579559b
TG
24#include "utf8.h"
25#include "util.h"
26#include "conf-parser.h"
12c2884c 27#include "firewall-util.h"
5a8bcb67
LP
28#include "networkd.h"
29#include "networkd-link.h"
f579559b 30
aba496a5
UTL
31static void address_init(Address *address) {
32 assert(address);
33
34 address->family = AF_UNSPEC;
35 address->scope = RT_SCOPE_UNIVERSE;
36 address->cinfo.ifa_prefered = CACHE_INFO_INFINITY_LIFE_TIME;
37 address->cinfo.ifa_valid = CACHE_INFO_INFINITY_LIFE_TIME;
38}
39
f048a16b 40int address_new_static(Network *network, unsigned section, Address **ret) {
f579559b
TG
41 _cleanup_address_free_ Address *address = NULL;
42
6ae115c1 43 if (section) {
16aa63a0 44 address = hashmap_get(network->addresses_by_section, UINT_TO_PTR(section));
6ae115c1
TG
45 if (address) {
46 *ret = address;
47 address = NULL;
48
49 return 0;
50 }
51 }
52
f579559b
TG
53 address = new0(Address, 1);
54 if (!address)
55 return -ENOMEM;
56
aba496a5 57 address_init(address);
801bd9e8 58
f579559b
TG
59 address->network = network;
60
1e39ff92 61 LIST_APPEND(addresses, network->static_addresses, address);
f579559b 62
6ae115c1
TG
63 if (section) {
64 address->section = section;
16aa63a0
TG
65 hashmap_put(network->addresses_by_section,
66 UINT_TO_PTR(address->section), address);
6ae115c1
TG
67 }
68
f579559b
TG
69 *ret = address;
70 address = NULL;
71
72 return 0;
73}
74
f048a16b
TG
75int address_new_dynamic(Address **ret) {
76 _cleanup_address_free_ Address *address = NULL;
77
78 address = new0(Address, 1);
79 if (!address)
80 return -ENOMEM;
81
aba496a5 82 address_init(address);
801bd9e8 83
f048a16b
TG
84 *ret = address;
85 address = NULL;
86
87 return 0;
88}
89
f579559b
TG
90void address_free(Address *address) {
91 if (!address)
92 return;
93
f048a16b 94 if (address->network) {
3d3d4255 95 LIST_REMOVE(addresses, address->network->static_addresses, address);
f579559b 96
f048a16b
TG
97 if (address->section)
98 hashmap_remove(address->network->addresses_by_section,
16aa63a0 99 UINT_TO_PTR(address->section));
f048a16b 100 }
6ae115c1 101
f579559b
TG
102 free(address);
103}
104
5a8bcb67
LP
105int address_establish(Address *address, Link *link) {
106 bool masq;
107 int r;
108
109 assert(address);
110 assert(link);
111
112 masq = link->network &&
113 link->network->ip_masquerade &&
114 address->family == AF_INET &&
115 address->scope < RT_SCOPE_LINK;
116
117 /* Add firewall entry if this is requested */
fd6d906c 118 if (address->ip_masquerade_done != masq) {
5a8bcb67
LP
119 union in_addr_union masked = address->in_addr;
120 in_addr_mask(address->family, &masked, address->prefixlen);
121
122 r = fw_add_masquerade(masq, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
123 if (r < 0)
124 log_link_warning_errno(link, r, "Could not enable IP masquerading: %m");
125
fd6d906c 126 address->ip_masquerade_done = masq;
5a8bcb67
LP
127 }
128
129 return 0;
130}
131
132int address_release(Address *address, Link *link) {
133 int r;
134
135 assert(address);
136 assert(link);
137
138 /* Remove masquerading firewall entry if it was added */
fd6d906c 139 if (address->ip_masquerade_done) {
5a8bcb67
LP
140 union in_addr_union masked = address->in_addr;
141 in_addr_mask(address->family, &masked, address->prefixlen);
142
143 r = fw_add_masquerade(false, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
144 if (r < 0)
145 log_link_warning_errno(link, r, "Failed to disable IP masquerading: %m");
146
fd6d906c 147 address->ip_masquerade_done = false;
5a8bcb67
LP
148 }
149
150 return 0;
151}
152
407fe036 153int address_drop(Address *address, Link *link,
1c4baffc
TG
154 sd_netlink_message_handler_t callback) {
155 _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
407fe036
TG
156 int r;
157
158 assert(address);
159 assert(address->family == AF_INET || address->family == AF_INET6);
160 assert(link);
161 assert(link->ifindex > 0);
162 assert(link->manager);
163 assert(link->manager->rtnl);
164
5a8bcb67
LP
165 address_release(address, link);
166
151b9b96
LP
167 r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_DELADDR,
168 link->ifindex, address->family);
eb56eb9b
MS
169 if (r < 0)
170 return log_error_errno(r, "Could not allocate RTM_DELADDR message: %m");
407fe036 171
5a723174 172 r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
eb56eb9b
MS
173 if (r < 0)
174 return log_error_errno(r, "Could not set prefixlen: %m");
5a723174 175
407fe036 176 if (address->family == AF_INET)
1c4baffc 177 r = sd_netlink_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
407fe036 178 else if (address->family == AF_INET6)
1c4baffc 179 r = sd_netlink_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
eb56eb9b
MS
180 if (r < 0)
181 return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
407fe036 182
1c4baffc 183 r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
eb56eb9b
MS
184 if (r < 0)
185 return log_error_errno(r, "Could not send rtnetlink message: %m");
407fe036 186
563c69c6
TG
187 link_ref(link);
188
407fe036
TG
189 return 0;
190}
191
aba496a5 192int address_update(Address *address, Link *link,
1c4baffc
TG
193 sd_netlink_message_handler_t callback) {
194 _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
aba496a5
UTL
195 int r;
196
197 assert(address);
198 assert(address->family == AF_INET || address->family == AF_INET6);
199 assert(link->ifindex > 0);
200 assert(link->manager);
201 assert(link->manager->rtnl);
202
203 r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req,
204 link->ifindex, address->family);
eb56eb9b
MS
205 if (r < 0)
206 return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
aba496a5
UTL
207
208 r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
eb56eb9b
MS
209 if (r < 0)
210 return log_error_errno(r, "Could not set prefixlen: %m");
aba496a5 211
851c9f82
PF
212 address->flags |= IFA_F_PERMANENT;
213
214 r = sd_rtnl_message_addr_set_flags(req, address->flags & 0xff);
eb56eb9b
MS
215 if (r < 0)
216 return log_error_errno(r, "Could not set flags: %m");
aba496a5 217
be3a09b7 218 if (address->flags & ~0xff && link->rtnl_extended_attrs) {
1c4baffc 219 r = sd_netlink_message_append_u32(req, IFA_FLAGS, address->flags);
851c9f82
PF
220 if (r < 0)
221 return log_error_errno(r, "Could not set extended flags: %m");
222 }
223
aba496a5 224 r = sd_rtnl_message_addr_set_scope(req, address->scope);
eb56eb9b
MS
225 if (r < 0)
226 return log_error_errno(r, "Could not set scope: %m");
aba496a5
UTL
227
228 if (address->family == AF_INET)
1c4baffc 229 r = sd_netlink_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
aba496a5 230 else if (address->family == AF_INET6)
1c4baffc 231 r = sd_netlink_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
eb56eb9b
MS
232 if (r < 0)
233 return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
aba496a5
UTL
234
235 if (address->family == AF_INET) {
1c4baffc 236 r = sd_netlink_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
eb56eb9b
MS
237 if (r < 0)
238 return log_error_errno(r, "Could not append IFA_BROADCAST attribute: %m");
aba496a5
UTL
239 }
240
241 if (address->label) {
1c4baffc 242 r = sd_netlink_message_append_string(req, IFA_LABEL, address->label);
eb56eb9b
MS
243 if (r < 0)
244 return log_error_errno(r, "Could not append IFA_LABEL attribute: %m");
aba496a5
UTL
245 }
246
1c4baffc 247 r = sd_netlink_message_append_cache_info(req, IFA_CACHEINFO, &address->cinfo);
eb56eb9b
MS
248 if (r < 0)
249 return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
aba496a5 250
1c4baffc 251 r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
eb56eb9b
MS
252 if (r < 0)
253 return log_error_errno(r, "Could not send rtnetlink message: %m");
aba496a5 254
563c69c6
TG
255 link_ref(link);
256
aba496a5
UTL
257 return 0;
258}
259
11bf3cce
LP
260static int address_acquire(Link *link, Address *original, Address **ret) {
261 union in_addr_union in_addr = {};
262 struct in_addr broadcast = {};
0099bc15 263 _cleanup_address_free_ Address *na = NULL;
11bf3cce
LP
264 int r;
265
266 assert(link);
267 assert(original);
268 assert(ret);
269
270 /* Something useful was configured? just use it */
af93291c 271 if (in_addr_is_null(original->family, &original->in_addr) <= 0)
11bf3cce
LP
272 return 0;
273
274 /* The address is configured to be 0.0.0.0 or [::] by the user?
275 * Then let's acquire something more useful from the pool. */
276 r = manager_address_pool_acquire(link->manager, original->family, original->prefixlen, &in_addr);
6a7a4e4d
LP
277 if (r < 0)
278 return log_link_error_errno(link, r, "Failed to acquire address from pool: %m");
11bf3cce 279 if (r == 0) {
79008bdd 280 log_link_error(link, "Couldn't find free address for interface, all taken.");
11bf3cce
LP
281 return -EBUSY;
282 }
283
284 if (original->family == AF_INET) {
d076c6f9 285 /* Pick first address in range for ourselves ... */
11bf3cce
LP
286 in_addr.in.s_addr = in_addr.in.s_addr | htobe32(1);
287
288 /* .. and use last as broadcast address */
289 broadcast.s_addr = in_addr.in.s_addr | htobe32(0xFFFFFFFFUL >> original->prefixlen);
290 } else if (original->family == AF_INET6)
291 in_addr.in6.s6_addr[15] |= 1;
292
293 r = address_new_dynamic(&na);
294 if (r < 0)
295 return r;
296
297 na->family = original->family;
298 na->prefixlen = original->prefixlen;
299 na->scope = original->scope;
300 na->cinfo = original->cinfo;
301
302 if (original->label) {
303 na->label = strdup(original->label);
0099bc15 304 if (!na->label)
11bf3cce 305 return -ENOMEM;
11bf3cce
LP
306 }
307
308 na->broadcast = broadcast;
309 na->in_addr = in_addr;
310
311 LIST_PREPEND(addresses, link->pool_addresses, na);
312
313 *ret = na;
0099bc15
SS
314 na = NULL;
315
11bf3cce
LP
316 return 0;
317}
318
f882c247 319int address_configure(Address *address, Link *link,
1c4baffc
TG
320 sd_netlink_message_handler_t callback) {
321 _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
f579559b
TG
322 int r;
323
c166a070
TG
324 assert(address);
325 assert(address->family == AF_INET || address->family == AF_INET6);
326 assert(link);
327 assert(link->ifindex > 0);
f882c247 328 assert(link->manager);
c166a070 329 assert(link->manager->rtnl);
f882c247 330
11bf3cce
LP
331 r = address_acquire(link, address, &address);
332 if (r < 0)
333 return r;
334
151b9b96
LP
335 r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR,
336 link->ifindex, address->family);
eb56eb9b
MS
337 if (r < 0)
338 return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
f579559b 339
5a723174 340 r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
eb56eb9b
MS
341 if (r < 0)
342 return log_error_errno(r, "Could not set prefixlen: %m");
5a723174 343
851c9f82
PF
344 address->flags |= IFA_F_PERMANENT;
345
346 r = sd_rtnl_message_addr_set_flags(req, (address->flags & 0xff));
eb56eb9b
MS
347 if (r < 0)
348 return log_error_errno(r, "Could not set flags: %m");
5a723174 349
851c9f82 350 if (address->flags & ~0xff) {
1c4baffc 351 r = sd_netlink_message_append_u32(req, IFA_FLAGS, address->flags);
851c9f82
PF
352 if (r < 0)
353 return log_error_errno(r, "Could not set extended flags: %m");
354 }
355
5c1d3fc9 356 r = sd_rtnl_message_addr_set_scope(req, address->scope);
eb56eb9b
MS
357 if (r < 0)
358 return log_error_errno(r, "Could not set scope: %m");
5a723174 359
0a0dc69b 360 if (address->family == AF_INET)
1c4baffc 361 r = sd_netlink_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
0a0dc69b 362 else if (address->family == AF_INET6)
1c4baffc 363 r = sd_netlink_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
eb56eb9b
MS
364 if (r < 0)
365 return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
f579559b 366
af93291c 367 if (!in_addr_is_null(address->family, &address->in_addr_peer)) {
c081882f 368 if (address->family == AF_INET)
1c4baffc 369 r = sd_netlink_message_append_in_addr(req, IFA_ADDRESS, &address->in_addr_peer.in);
c081882f 370 else if (address->family == AF_INET6)
1c4baffc 371 r = sd_netlink_message_append_in6_addr(req, IFA_ADDRESS, &address->in_addr_peer.in6);
eb56eb9b
MS
372 if (r < 0)
373 return log_error_errno(r, "Could not append IFA_ADDRESS attribute: %m");
c081882f
SS
374 } else {
375 if (address->family == AF_INET) {
1c4baffc 376 r = sd_netlink_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
eb56eb9b
MS
377 if (r < 0)
378 return log_error_errno(r, "Could not append IFA_BROADCAST attribute: %m");
c081882f 379 }
f579559b
TG
380 }
381
382 if (address->label) {
1c4baffc 383 r = sd_netlink_message_append_string(req, IFA_LABEL, address->label);
eb56eb9b
MS
384 if (r < 0)
385 return log_error_errno(r, "Could not append IFA_LABEL attribute: %m");
f579559b
TG
386 }
387
1c4baffc 388 r = sd_netlink_message_append_cache_info(req, IFA_CACHEINFO,
68ceb9df 389 &address->cinfo);
eb56eb9b
MS
390 if (r < 0)
391 return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
68ceb9df 392
1c4baffc 393 r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
eb56eb9b
MS
394 if (r < 0)
395 return log_error_errno(r, "Could not send rtnetlink message: %m");
f579559b 396
563c69c6
TG
397 link_ref(link);
398
5a8bcb67
LP
399 address_establish(address, link);
400
f579559b
TG
401 return 0;
402}
403
44e7b949
LP
404int config_parse_broadcast(
405 const char *unit,
eb0ea358
TG
406 const char *filename,
407 unsigned line,
408 const char *section,
409 unsigned section_line,
410 const char *lvalue,
411 int ltype,
412 const char *rvalue,
413 void *data,
414 void *userdata) {
44e7b949 415
eb0ea358
TG
416 Network *network = userdata;
417 _cleanup_address_free_ Address *n = NULL;
eb0ea358
TG
418 int r;
419
420 assert(filename);
421 assert(section);
422 assert(lvalue);
423 assert(rvalue);
424 assert(data);
425
426 r = address_new_static(network, section_line, &n);
427 if (r < 0)
428 return r;
429
482e2ac1
TG
430 if (n->family == AF_INET6) {
431 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
44e7b949 432 "Broadcast is not valid for IPv6 addresses, ignoring assignment: %s", rvalue);
482e2ac1
TG
433 return 0;
434 }
435
44e7b949 436 r = in_addr_from_string(AF_INET, rvalue, (union in_addr_union*) &n->broadcast);
eb0ea358
TG
437 if (r < 0) {
438 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
44e7b949 439 "Broadcast is invalid, ignoring assignment: %s", rvalue);
eb0ea358
TG
440 return 0;
441 }
442
44e7b949 443 n->family = AF_INET;
eb0ea358
TG
444 n = NULL;
445
446 return 0;
447}
448
f579559b
TG
449int config_parse_address(const char *unit,
450 const char *filename,
451 unsigned line,
452 const char *section,
71a61510 453 unsigned section_line,
f579559b
TG
454 const char *lvalue,
455 int ltype,
456 const char *rvalue,
457 void *data,
458 void *userdata) {
44e7b949 459
6ae115c1 460 Network *network = userdata;
f579559b 461 _cleanup_address_free_ Address *n = NULL;
44e7b949
LP
462 const char *address, *e;
463 union in_addr_union buffer;
464 int r, f;
f579559b
TG
465
466 assert(filename);
6ae115c1 467 assert(section);
f579559b
TG
468 assert(lvalue);
469 assert(rvalue);
470 assert(data);
471
92fe133a
TG
472 if (streq(section, "Network")) {
473 /* we are not in an Address section, so treat
474 * this as the special '0' section */
475 section_line = 0;
476 }
477
f048a16b 478 r = address_new_static(network, section_line, &n);
f579559b
TG
479 if (r < 0)
480 return r;
481
482 /* Address=address/prefixlen */
483
484 /* prefixlen */
485 e = strchr(rvalue, '/');
486 if (e) {
487 unsigned i;
488 r = safe_atou(e + 1, &i);
489 if (r < 0) {
490 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
a2a85a22 491 "Prefix length is invalid, ignoring assignment: %s", e + 1);
f579559b
TG
492 return 0;
493 }
494
495 n->prefixlen = (unsigned char) i;
8cd11a0f 496
44e7b949
LP
497 address = strndupa(rvalue, e - rvalue);
498 } else
499 address = rvalue;
f579559b 500
44e7b949 501 r = in_addr_from_string_auto(address, &f, &buffer);
f579559b
TG
502 if (r < 0) {
503 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
504 "Address is invalid, ignoring assignment: %s", address);
505 return 0;
506 }
507
a2a85a22
TG
508 if (!e && f == AF_INET) {
509 r = in_addr_default_prefixlen(&buffer.in, &n->prefixlen);
510 if (r < 0) {
511 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
512 "Prefix length not specified, and a default one can not be deduced for '%s', ignoring assignment", address);
513 return 0;
514 }
515 }
516
44e7b949
LP
517 if (n->family != AF_UNSPEC && f != n->family) {
518 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
519 "Address is incompatible, ignoring assignment: %s", address);
520 return 0;
521 }
522
523 n->family = f;
524
525 if (streq(lvalue, "Address"))
526 n->in_addr = buffer;
527 else
528 n->in_addr_peer = buffer;
529
530 if (n->family == AF_INET && n->broadcast.s_addr == 0)
531 n->broadcast.s_addr = n->in_addr.in.s_addr | htonl(0xfffffffflu >> n->prefixlen);
eb0ea358 532
f579559b
TG
533 n = NULL;
534
535 return 0;
536}
6ae115c1
TG
537
538int config_parse_label(const char *unit,
539 const char *filename,
540 unsigned line,
541 const char *section,
542 unsigned section_line,
543 const char *lvalue,
544 int ltype,
545 const char *rvalue,
546 void *data,
547 void *userdata) {
548 Network *network = userdata;
549 _cleanup_address_free_ Address *n = NULL;
6ae115c1
TG
550 char *label;
551 int r;
552
553 assert(filename);
554 assert(section);
555 assert(lvalue);
556 assert(rvalue);
557 assert(data);
558
f048a16b 559 r = address_new_static(network, section_line, &n);
6ae115c1
TG
560 if (r < 0)
561 return r;
562
563 label = strdup(rvalue);
564 if (!label)
565 return log_oom();
566
567 if (!ascii_is_valid(label) || strlen(label) >= IFNAMSIZ) {
568 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
569 "Interface label is not ASCII clean or is too"
570 " long, ignoring assignment: %s", rvalue);
571 free(label);
572 return 0;
573 }
574
575 free(n->label);
576 if (*label)
577 n->label = label;
578 else {
579 free(label);
580 n->label = NULL;
581 }
582
583 n = NULL;
584
585 return 0;
586}
9505d3c6
TG
587
588bool address_equal(Address *a1, Address *a2) {
589 /* same object */
590 if (a1 == a2)
591 return true;
592
593 /* one, but not both, is NULL */
594 if (!a1 || !a2)
595 return false;
596
597 if (a1->family != a2->family)
598 return false;
599
600 switch (a1->family) {
601 /* use the same notion of equality as the kernel does */
602 case AF_UNSPEC:
603 return true;
604
605 case AF_INET:
606 if (a1->prefixlen != a2->prefixlen)
607 return false;
6cb8e687
ZJS
608 else if (a1->prefixlen == 0)
609 /* make sure we don't try to shift by 32.
610 * See ISO/IEC 9899:TC3 § 6.5.7.3. */
611 return true;
9505d3c6
TG
612 else {
613 uint32_t b1, b2;
614
615 b1 = be32toh(a1->in_addr.in.s_addr);
616 b2 = be32toh(a2->in_addr.in.s_addr);
617
618 return (b1 >> (32 - a1->prefixlen)) == (b2 >> (32 - a1->prefixlen));
619 }
620
5a8bcb67 621 case AF_INET6: {
9505d3c6
TG
622 uint64_t *b1, *b2;
623
624 b1 = (uint64_t*)&a1->in_addr.in6;
625 b2 = (uint64_t*)&a2->in_addr.in6;
626
627 return (((b1[0] ^ b2[0]) | (b1[1] ^ b2[1])) == 0UL);
628 }
5a8bcb67 629
9505d3c6
TG
630 default:
631 assert_not_reached("Invalid address family");
632 }
633}