Line data Source code
1 : /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2 :
3 : /***
4 : This file is part of systemd.
5 :
6 : Copyright 2013-2014 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 <netinet/ether.h>
23 : #include <linux/if.h>
24 :
25 : #include "hostname-util.h"
26 : #include "networkd-link.h"
27 : #include "network-internal.h"
28 : #include "dhcp-lease-internal.h"
29 :
30 0 : static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m,
31 : void *userdata) {
32 0 : _cleanup_link_unref_ Link *link = userdata;
33 : int r;
34 :
35 0 : assert(link);
36 0 : assert(link->dhcp4_messages);
37 :
38 0 : link->dhcp4_messages --;
39 :
40 0 : r = sd_netlink_message_get_errno(m);
41 0 : if (r < 0 && r != -EEXIST) {
42 0 : log_link_error(link, "could not set DHCPv4 route: %s",
43 : strerror(-r));
44 0 : link_enter_failed(link);
45 : }
46 :
47 0 : if (!link->dhcp4_messages) {
48 0 : link->dhcp4_configured = true;
49 0 : link_client_handler(link);
50 : }
51 :
52 0 : return 1;
53 : }
54 :
55 0 : static int link_set_dhcp_routes(Link *link) {
56 : struct in_addr gateway;
57 : struct sd_dhcp_route *static_routes;
58 : int r, n, i;
59 :
60 0 : assert(link);
61 0 : assert(link->dhcp_lease);
62 :
63 0 : r = sd_dhcp_lease_get_router(link->dhcp_lease, &gateway);
64 0 : if (r < 0 && r != -ENOENT) {
65 0 : log_link_warning(link,
66 : "DHCP error: could not get gateway: %s",
67 : strerror(-r));
68 0 : return r;
69 : }
70 0 : if (r >= 0) {
71 : struct in_addr address;
72 0 : _cleanup_route_free_ Route *route = NULL;
73 0 : _cleanup_route_free_ Route *route_gw = NULL;
74 :
75 0 : r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
76 0 : if (r < 0) {
77 0 : log_link_warning(link,
78 : "DHCP error: could not get address: %s",
79 : strerror(-r));
80 0 : return r;
81 : }
82 :
83 0 : r = route_new_dynamic(&route, RTPROT_DHCP);
84 0 : if (r < 0) {
85 0 : log_link_error(link,
86 : "Could not allocate route: %s",
87 : strerror(-r));
88 0 : return r;
89 : }
90 :
91 0 : r = route_new_dynamic(&route_gw, RTPROT_DHCP);
92 0 : if (r < 0) {
93 0 : log_link_error(link,
94 : "Could not allocate route: %s",
95 : strerror(-r));
96 0 : return r;
97 : }
98 :
99 : /* The dhcp netmask may mask out the gateway. Add an explicit
100 : * route for the gw host so that we can route no matter the
101 : * netmask or existing kernel route tables. */
102 0 : route_gw->family = AF_INET;
103 0 : route_gw->dst_addr.in = gateway;
104 0 : route_gw->dst_prefixlen = 32;
105 0 : route_gw->prefsrc_addr.in = address;
106 0 : route_gw->scope = RT_SCOPE_LINK;
107 0 : route_gw->metrics = link->network->dhcp_route_metric;
108 :
109 0 : r = route_configure(route_gw, link, &dhcp4_route_handler);
110 0 : if (r < 0) {
111 0 : log_link_warning(link,
112 : "could not set host route: %s",
113 : strerror(-r));
114 0 : return r;
115 : }
116 :
117 0 : link->dhcp4_messages ++;
118 :
119 0 : route->family = AF_INET;
120 0 : route->in_addr.in = gateway;
121 0 : route->prefsrc_addr.in = address;
122 0 : route->metrics = link->network->dhcp_route_metric;
123 :
124 0 : r = route_configure(route, link, &dhcp4_route_handler);
125 0 : if (r < 0) {
126 0 : log_link_warning(link,
127 : "could not set routes: %s",
128 : strerror(-r));
129 0 : link_enter_failed(link);
130 0 : return r;
131 : }
132 :
133 0 : link->dhcp4_messages ++;
134 : }
135 :
136 0 : n = sd_dhcp_lease_get_routes(link->dhcp_lease, &static_routes);
137 0 : if (n == -ENOENT)
138 0 : return 0;
139 0 : if (n < 0) {
140 0 : log_link_warning(link,
141 : "DHCP error: could not get routes: %s",
142 : strerror(-n));
143 :
144 0 : return n;
145 : }
146 :
147 0 : for (i = 0; i < n; i++) {
148 0 : _cleanup_route_free_ Route *route = NULL;
149 :
150 0 : r = route_new_dynamic(&route, RTPROT_DHCP);
151 0 : if (r < 0) {
152 0 : log_link_error(link, "Could not allocate route: %s",
153 : strerror(-r));
154 0 : return r;
155 : }
156 :
157 0 : route->family = AF_INET;
158 0 : route->in_addr.in = static_routes[i].gw_addr;
159 0 : route->dst_addr.in = static_routes[i].dst_addr;
160 0 : route->dst_prefixlen = static_routes[i].dst_prefixlen;
161 0 : route->metrics = link->network->dhcp_route_metric;
162 :
163 0 : r = route_configure(route, link, &dhcp4_route_handler);
164 0 : if (r < 0) {
165 0 : log_link_warning(link,
166 : "could not set host route: %s",
167 : strerror(-r));
168 0 : return r;
169 : }
170 :
171 0 : link->dhcp4_messages ++;
172 : }
173 :
174 0 : return 0;
175 : }
176 :
177 0 : static int dhcp_lease_lost(Link *link) {
178 0 : _cleanup_address_free_ Address *address = NULL;
179 : struct in_addr addr;
180 : struct in_addr netmask;
181 : struct in_addr gateway;
182 0 : unsigned prefixlen = 0;
183 : int r;
184 :
185 0 : assert(link);
186 0 : assert(link->dhcp_lease);
187 :
188 0 : log_link_warning(link, "DHCP lease lost");
189 :
190 0 : if (link->network->dhcp_routes) {
191 : struct sd_dhcp_route *routes;
192 : int n, i;
193 :
194 0 : n = sd_dhcp_lease_get_routes(link->dhcp_lease, &routes);
195 0 : if (n >= 0) {
196 0 : for (i = 0; i < n; i++) {
197 0 : _cleanup_route_free_ Route *route = NULL;
198 :
199 0 : r = route_new_dynamic(&route, RTPROT_UNSPEC);
200 0 : if (r >= 0) {
201 0 : route->family = AF_INET;
202 0 : route->in_addr.in = routes[i].gw_addr;
203 0 : route->dst_addr.in = routes[i].dst_addr;
204 0 : route->dst_prefixlen = routes[i].dst_prefixlen;
205 :
206 0 : route_drop(route, link,
207 : &link_route_drop_handler);
208 : }
209 : }
210 : }
211 : }
212 :
213 0 : r = address_new_dynamic(&address);
214 0 : if (r >= 0) {
215 0 : r = sd_dhcp_lease_get_router(link->dhcp_lease, &gateway);
216 0 : if (r >= 0) {
217 0 : _cleanup_route_free_ Route *route_gw = NULL;
218 0 : _cleanup_route_free_ Route *route = NULL;
219 :
220 0 : r = route_new_dynamic(&route_gw, RTPROT_UNSPEC);
221 0 : if (r >= 0) {
222 0 : route_gw->family = AF_INET;
223 0 : route_gw->dst_addr.in = gateway;
224 0 : route_gw->dst_prefixlen = 32;
225 0 : route_gw->scope = RT_SCOPE_LINK;
226 :
227 0 : route_drop(route_gw, link,
228 : &link_route_drop_handler);
229 : }
230 :
231 0 : r = route_new_dynamic(&route, RTPROT_UNSPEC);
232 0 : if (r >= 0) {
233 0 : route->family = AF_INET;
234 0 : route->in_addr.in = gateway;
235 :
236 0 : route_drop(route, link,
237 : &link_route_drop_handler);
238 : }
239 : }
240 :
241 0 : r = sd_dhcp_lease_get_address(link->dhcp_lease, &addr);
242 0 : if (r >= 0) {
243 0 : r = sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask);
244 0 : if (r >= 0)
245 0 : prefixlen = in_addr_netmask_to_prefixlen(&netmask);
246 :
247 0 : address->family = AF_INET;
248 0 : address->in_addr.in = addr;
249 0 : address->prefixlen = prefixlen;
250 :
251 0 : address_drop(address, link, &link_address_drop_handler);
252 : }
253 : }
254 :
255 0 : if (link->network->dhcp_mtu) {
256 : uint16_t mtu;
257 :
258 0 : r = sd_dhcp_lease_get_mtu(link->dhcp_lease, &mtu);
259 0 : if (r >= 0 && link->original_mtu != mtu) {
260 0 : r = link_set_mtu(link, link->original_mtu);
261 0 : if (r < 0) {
262 0 : log_link_warning(link,
263 : "DHCP error: could not reset MTU");
264 0 : link_enter_failed(link);
265 0 : return r;
266 : }
267 : }
268 : }
269 :
270 0 : if (link->network->dhcp_hostname) {
271 0 : const char *hostname = NULL;
272 :
273 0 : if (!link->network->hostname)
274 0 : r = sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname);
275 : else
276 0 : hostname = link->network->hostname;
277 :
278 0 : if (r >= 0 || hostname) {
279 0 : r = link_set_hostname(link, hostname);
280 0 : if (r < 0)
281 0 : log_link_error_errno(link, r,
282 : "Failed to set transient hostname to '%s': %m",
283 : hostname);
284 :
285 : }
286 : }
287 :
288 0 : link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease);
289 0 : link->dhcp4_configured = false;
290 :
291 0 : return 0;
292 : }
293 :
294 0 : static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m,
295 : void *userdata) {
296 0 : _cleanup_link_unref_ Link *link = userdata;
297 : int r;
298 :
299 0 : assert(link);
300 :
301 0 : r = sd_netlink_message_get_errno(m);
302 0 : if (r < 0 && r != -EEXIST) {
303 0 : log_link_error(link, "could not set DHCPv4 address: %s",
304 : strerror(-r));
305 0 : link_enter_failed(link);
306 0 : } else if (r >= 0)
307 0 : link_rtnl_process_address(rtnl, m, link->manager);
308 :
309 0 : link_set_dhcp_routes(link);
310 :
311 0 : return 1;
312 : }
313 :
314 0 : static int dhcp4_update_address(Link *link,
315 : struct in_addr *address,
316 : struct in_addr *netmask,
317 : uint32_t lifetime) {
318 0 : _cleanup_address_free_ Address *addr = NULL;
319 : unsigned prefixlen;
320 : int r;
321 :
322 0 : assert(address);
323 0 : assert(netmask);
324 0 : assert(lifetime);
325 :
326 0 : prefixlen = in_addr_netmask_to_prefixlen(netmask);
327 :
328 0 : r = address_new_dynamic(&addr);
329 0 : if (r < 0)
330 0 : return r;
331 :
332 0 : addr->family = AF_INET;
333 0 : addr->in_addr.in.s_addr = address->s_addr;
334 0 : addr->cinfo.ifa_prefered = lifetime;
335 0 : addr->cinfo.ifa_valid = lifetime;
336 0 : addr->prefixlen = prefixlen;
337 0 : addr->broadcast.s_addr = address->s_addr | ~netmask->s_addr;
338 :
339 : /* use update rather than configure so that we will update the
340 : * lifetime of an existing address if it has already been configured */
341 0 : r = address_update(addr, link, &dhcp4_address_handler);
342 0 : if (r < 0)
343 0 : return r;
344 :
345 0 : return 0;
346 : }
347 :
348 0 : static int dhcp_lease_renew(sd_dhcp_client *client, Link *link) {
349 : sd_dhcp_lease *lease;
350 : struct in_addr address;
351 : struct in_addr netmask;
352 0 : uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME;
353 : int r;
354 :
355 0 : assert(link);
356 0 : assert(client);
357 0 : assert(link->network);
358 :
359 0 : r = sd_dhcp_client_get_lease(client, &lease);
360 0 : if (r < 0) {
361 0 : log_link_warning(link, "DHCP error: no lease %s",
362 : strerror(-r));
363 0 : return r;
364 : }
365 :
366 0 : sd_dhcp_lease_unref(link->dhcp_lease);
367 0 : link->dhcp4_configured = false;
368 0 : link->dhcp_lease = lease;
369 :
370 0 : r = sd_dhcp_lease_get_address(lease, &address);
371 0 : if (r < 0) {
372 0 : log_link_warning(link, "DHCP error: no address: %s",
373 : strerror(-r));
374 0 : return r;
375 : }
376 :
377 0 : r = sd_dhcp_lease_get_netmask(lease, &netmask);
378 0 : if (r < 0) {
379 0 : log_link_warning(link, "DHCP error: no netmask: %s",
380 : strerror(-r));
381 0 : return r;
382 : }
383 :
384 0 : if (!link->network->dhcp_critical) {
385 0 : r = sd_dhcp_lease_get_lifetime(link->dhcp_lease,
386 : &lifetime);
387 0 : if (r < 0) {
388 0 : log_link_warning(link,
389 : "DHCP error: no lifetime: %s",
390 : strerror(-r));
391 0 : return r;
392 : }
393 : }
394 :
395 0 : r = dhcp4_update_address(link, &address, &netmask, lifetime);
396 0 : if (r < 0) {
397 0 : log_link_warning(link, "could not update IP address: %s",
398 : strerror(-r));
399 0 : link_enter_failed(link);
400 0 : return r;
401 : }
402 :
403 0 : return 0;
404 : }
405 :
406 0 : static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
407 : sd_dhcp_lease *lease;
408 : struct in_addr address;
409 : struct in_addr netmask;
410 : struct in_addr gateway;
411 : unsigned prefixlen;
412 0 : uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME;
413 : int r;
414 :
415 0 : assert(client);
416 0 : assert(link);
417 :
418 0 : r = sd_dhcp_client_get_lease(client, &lease);
419 0 : if (r < 0)
420 0 : return log_link_error_errno(link, r, "DHCP error: no lease: %m");
421 :
422 0 : r = sd_dhcp_lease_get_address(lease, &address);
423 0 : if (r < 0)
424 0 : return log_link_error_errno(link, r, "DHCP error: no address: %m");
425 :
426 0 : r = sd_dhcp_lease_get_netmask(lease, &netmask);
427 0 : if (r < 0)
428 0 : return log_link_error_errno(link, r, "DHCP error: no netmask: %m");
429 :
430 0 : prefixlen = in_addr_netmask_to_prefixlen(&netmask);
431 :
432 0 : r = sd_dhcp_lease_get_router(lease, &gateway);
433 0 : if (r < 0 && r != -ENOENT)
434 0 : return log_link_error_errno(link, r, "DHCP error: could not get gateway: %m");
435 :
436 0 : if (r >= 0)
437 0 : log_struct(LOG_INFO,
438 : LOG_LINK_INTERFACE(link),
439 : LOG_LINK_MESSAGE(link, "DHCPv4 address %u.%u.%u.%u/%u via %u.%u.%u.%u",
440 : ADDRESS_FMT_VAL(address),
441 : prefixlen,
442 : ADDRESS_FMT_VAL(gateway)),
443 : "ADDRESS=%u.%u.%u.%u", ADDRESS_FMT_VAL(address),
444 : "PREFIXLEN=%u", prefixlen,
445 : "GATEWAY=%u.%u.%u.%u", ADDRESS_FMT_VAL(gateway),
446 : NULL);
447 : else
448 0 : log_struct(LOG_INFO,
449 : LOG_LINK_INTERFACE(link),
450 : LOG_LINK_MESSAGE(link, "DHCPv4 address %u.%u.%u.%u/%u",
451 : ADDRESS_FMT_VAL(address),
452 : prefixlen),
453 : "ADDRESS=%u.%u.%u.%u", ADDRESS_FMT_VAL(address),
454 : "PREFIXLEN=%u", prefixlen,
455 : NULL);
456 :
457 0 : link->dhcp_lease = lease;
458 :
459 0 : if (link->network->dhcp_mtu) {
460 : uint16_t mtu;
461 :
462 0 : r = sd_dhcp_lease_get_mtu(lease, &mtu);
463 0 : if (r >= 0) {
464 0 : r = link_set_mtu(link, mtu);
465 0 : if (r < 0)
466 0 : log_link_error_errno(link, r, "Failed to set MTU to %" PRIu16 ": %m", mtu);
467 : }
468 : }
469 :
470 0 : if (link->network->dhcp_hostname) {
471 : const char *hostname;
472 :
473 0 : if (!link->network->hostname)
474 0 : r = sd_dhcp_lease_get_hostname(lease, &hostname);
475 : else
476 0 : hostname = link->network->hostname;
477 :
478 0 : if (r >= 0 || hostname) {
479 0 : r = link_set_hostname(link, hostname);
480 0 : if (r < 0)
481 0 : log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname);
482 : }
483 : }
484 :
485 0 : if (!link->network->dhcp_critical) {
486 0 : r = sd_dhcp_lease_get_lifetime(link->dhcp_lease, &lifetime);
487 0 : if (r < 0) {
488 0 : log_link_warning_errno(link, r, "DHCP error: no lifetime: %m");
489 0 : return r;
490 : }
491 : }
492 :
493 0 : r = dhcp4_update_address(link, &address, &netmask, lifetime);
494 0 : if (r < 0) {
495 0 : log_link_warning_errno(link, r, "Could not update IP address: %m");
496 0 : link_enter_failed(link);
497 0 : return r;
498 : }
499 :
500 0 : return 0;
501 : }
502 0 : static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
503 0 : Link *link = userdata;
504 0 : int r = 0;
505 :
506 0 : assert(link);
507 0 : assert(link->network);
508 0 : assert(link->manager);
509 :
510 0 : if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
511 0 : return;
512 :
513 0 : switch (event) {
514 : case DHCP_EVENT_EXPIRED:
515 : case DHCP_EVENT_STOP:
516 : case DHCP_EVENT_IP_CHANGE:
517 0 : if (link->network->dhcp_critical) {
518 0 : log_link_error(link,
519 : "DHCPv4 connection considered system critical, ignoring request to reconfigure it.");
520 0 : return;
521 : }
522 :
523 0 : if (link->dhcp_lease) {
524 0 : r = dhcp_lease_lost(link);
525 0 : if (r < 0) {
526 0 : link_enter_failed(link);
527 0 : return;
528 : }
529 : }
530 :
531 0 : if (event == DHCP_EVENT_IP_CHANGE) {
532 0 : r = dhcp_lease_acquired(client, link);
533 0 : if (r < 0) {
534 0 : link_enter_failed(link);
535 0 : return;
536 : }
537 : }
538 :
539 0 : break;
540 : case DHCP_EVENT_RENEW:
541 0 : r = dhcp_lease_renew(client, link);
542 0 : if (r < 0) {
543 0 : link_enter_failed(link);
544 0 : return;
545 : }
546 0 : break;
547 : case DHCP_EVENT_IP_ACQUIRE:
548 0 : r = dhcp_lease_acquired(client, link);
549 0 : if (r < 0) {
550 0 : link_enter_failed(link);
551 0 : return;
552 : }
553 0 : break;
554 : default:
555 0 : if (event < 0)
556 0 : log_link_warning(link,
557 : "DHCP error: client failed: %s",
558 : strerror(-event));
559 : else
560 0 : log_link_warning(link,
561 : "DHCP unknown event: %d",
562 : event);
563 0 : break;
564 : }
565 :
566 0 : return;
567 : }
568 :
569 0 : int dhcp4_configure(Link *link) {
570 : int r;
571 :
572 0 : assert(link);
573 0 : assert(link->network);
574 0 : assert(link->network->dhcp & ADDRESS_FAMILY_IPV4);
575 :
576 0 : r = sd_dhcp_client_new(&link->dhcp_client);
577 0 : if (r < 0)
578 0 : return r;
579 :
580 0 : r = sd_dhcp_client_attach_event(link->dhcp_client, NULL, 0);
581 0 : if (r < 0)
582 0 : return r;
583 :
584 0 : r = sd_dhcp_client_set_mac(link->dhcp_client,
585 0 : (const uint8_t *) &link->mac,
586 : sizeof (link->mac), ARPHRD_ETHER);
587 0 : if (r < 0)
588 0 : return r;
589 :
590 0 : r = sd_dhcp_client_set_index(link->dhcp_client, link->ifindex);
591 0 : if (r < 0)
592 0 : return r;
593 :
594 0 : r = sd_dhcp_client_set_callback(link->dhcp_client, dhcp4_handler, link);
595 0 : if (r < 0)
596 0 : return r;
597 :
598 0 : r = sd_dhcp_client_set_request_broadcast(link->dhcp_client,
599 0 : link->network->dhcp_broadcast);
600 0 : if (r < 0)
601 0 : return r;
602 :
603 0 : if (link->mtu) {
604 0 : r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu);
605 0 : if (r < 0)
606 0 : return r;
607 : }
608 :
609 0 : if (link->network->dhcp_mtu) {
610 0 : r = sd_dhcp_client_set_request_option(link->dhcp_client,
611 : DHCP_OPTION_INTERFACE_MTU);
612 0 : if (r < 0)
613 0 : return r;
614 : }
615 :
616 0 : if (link->network->dhcp_routes) {
617 0 : r = sd_dhcp_client_set_request_option(link->dhcp_client,
618 : DHCP_OPTION_STATIC_ROUTE);
619 0 : if (r < 0)
620 0 : return r;
621 0 : r = sd_dhcp_client_set_request_option(link->dhcp_client,
622 : DHCP_OPTION_CLASSLESS_STATIC_ROUTE);
623 0 : if (r < 0)
624 0 : return r;
625 : }
626 :
627 0 : if (link->network->dhcp_sendhost) {
628 0 : _cleanup_free_ char *hostname = NULL;
629 0 : const char *hn = NULL;
630 :
631 0 : if (!link->network->hostname) {
632 0 : hostname = gethostname_malloc();
633 0 : if (!hostname)
634 0 : return -ENOMEM;
635 :
636 0 : hn = hostname;
637 : } else
638 0 : hn = link->network->hostname;
639 :
640 0 : if (!is_localhost(hn)) {
641 0 : r = sd_dhcp_client_set_hostname(link->dhcp_client, hn);
642 0 : if (r < 0)
643 0 : return r;
644 : }
645 : }
646 :
647 0 : if (link->network->dhcp_vendor_class_identifier) {
648 0 : r = sd_dhcp_client_set_vendor_class_identifier(link->dhcp_client,
649 0 : link->network->dhcp_vendor_class_identifier);
650 0 : if (r < 0)
651 0 : return r;
652 : }
653 :
654 0 : switch (link->network->dhcp_client_identifier) {
655 : case DHCP_CLIENT_ID_DUID:
656 : /* Library defaults to this. */
657 0 : break;
658 : case DHCP_CLIENT_ID_MAC:
659 0 : r = sd_dhcp_client_set_client_id(link->dhcp_client,
660 : ARPHRD_ETHER,
661 0 : (const uint8_t *) &link->mac,
662 : sizeof (link->mac));
663 0 : if (r < 0)
664 0 : return r;
665 0 : break;
666 : default:
667 0 : assert_not_reached("Unknown client identifier type.");
668 : }
669 :
670 0 : return 0;
671 : }
|