chiark / gitweb /
431801d6f0e76d992e9f66e9dd9d81583559d649
[elogind.git] / src / libsystemd-network / sd-dhcp6-client.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2014 Intel Corporation. All rights reserved.
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 <errno.h>
23 #include <string.h>
24 #include <sys/ioctl.h>
25
26 #include "udev.h"
27 #include "udev-util.h"
28 #include "virt.h"
29 #include "siphash24.h"
30 #include "util.h"
31 #include "refcnt.h"
32
33 #include "network-internal.h"
34 #include "sd-dhcp6-client.h"
35 #include "dhcp6-protocol.h"
36 #include "dhcp6-internal.h"
37 #include "dhcp6-lease-internal.h"
38
39 #define SYSTEMD_PEN 43793
40 #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
41
42 struct sd_dhcp6_client {
43         RefCount n_ref;
44
45         enum DHCP6State state;
46         sd_event *event;
47         int event_priority;
48         int index;
49         struct ether_addr mac_addr;
50         DHCP6IA ia_na;
51         be32_t transaction_id;
52         struct sd_dhcp6_lease *lease;
53         int fd;
54         sd_event_source *receive_message;
55         usec_t retransmit_time;
56         uint8_t retransmit_count;
57         sd_event_source *timeout_resend;
58         sd_event_source *timeout_resend_expire;
59         sd_dhcp6_client_cb_t cb;
60         void *userdata;
61
62         struct duid_en {
63                 uint16_t type; /* DHCP6_DUID_EN */
64                 uint32_t pen;
65                 uint8_t id[8];
66         } _packed_ duid;
67 };
68
69 const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
70         [DHCP6_SOLICIT] = "SOLICIT",
71         [DHCP6_ADVERTISE] = "ADVERTISE",
72         [DHCP6_REQUEST] = "REQUEST",
73         [DHCP6_CONFIRM] = "CONFIRM",
74         [DHCP6_RENEW] = "RENEW",
75         [DHCP6_REBIND] = "REBIND",
76         [DHCP6_REPLY] = "REPLY",
77         [DHCP6_RELEASE] = "RELEASE",
78         [DHCP6_DECLINE] = "DECLINE",
79         [DHCP6_RECONFIGURE] = "RECONFIGURE",
80         [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
81         [DHCP6_RELAY_FORW] = "RELAY-FORW",
82         [DHCP6_RELAY_REPL] = "RELAY-REPL",
83 };
84
85 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
86
87 const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
88         [DHCP6_STATUS_SUCCESS] = "Success",
89         [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
90         [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
91         [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
92         [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
93         [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
94 };
95
96 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
97
98 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
99                                  sd_dhcp6_client_cb_t cb, void *userdata)
100 {
101         assert_return(client, -EINVAL);
102
103         client->cb = cb;
104         client->userdata = userdata;
105
106         return 0;
107 }
108
109 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
110 {
111         assert_return(client, -EINVAL);
112         assert_return(interface_index >= -1, -EINVAL);
113
114         client->index = interface_index;
115
116         return 0;
117 }
118
119 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
120                             const struct ether_addr *mac_addr)
121 {
122         assert_return(client, -EINVAL);
123
124         if (mac_addr)
125                 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
126         else
127                 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
128
129         return 0;
130 }
131
132 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
133         if (client->cb) {
134                 client = sd_dhcp6_client_ref(client);
135                 client->cb(client, event, client->userdata);
136                 client = sd_dhcp6_client_unref(client);
137         }
138
139         return client;
140 }
141
142 static int client_initialize(sd_dhcp6_client *client)
143 {
144         assert_return(client, -EINVAL);
145
146         client->receive_message =
147                 sd_event_source_unref(client->receive_message);
148
149         if (client->fd > 0)
150                 client->fd = safe_close(client->fd);
151
152         client->transaction_id = random_u32() & 0x00ffffff;
153
154         client->ia_na.timeout_t1 =
155                 sd_event_source_unref(client->ia_na.timeout_t1);
156         client->ia_na.timeout_t2 =
157                 sd_event_source_unref(client->ia_na.timeout_t2);
158
159         client->retransmit_time = 0;
160         client->retransmit_count = 0;
161         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
162         client->timeout_resend_expire =
163                 sd_event_source_unref(client->timeout_resend_expire);
164
165         client->state = DHCP6_STATE_STOPPED;
166
167         return 0;
168 }
169
170 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
171         assert_return(client, NULL);
172
173         client = client_notify(client, error);
174         if (client)
175                 client_initialize(client);
176
177         return client;
178 }
179
180 static int client_send_message(sd_dhcp6_client *client) {
181         _cleanup_free_ DHCP6Message *message = NULL;
182         struct in6_addr all_servers =
183                 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
184         size_t len, optlen = 512;
185         uint8_t *opt;
186         int r;
187
188         len = sizeof(DHCP6Message) + optlen;
189
190         message = malloc0(len);
191         if (!message)
192                 return -ENOMEM;
193
194         opt = (uint8_t *)(message + 1);
195
196         message->transaction_id = client->transaction_id;
197
198         switch(client->state) {
199         case DHCP6_STATE_SOLICITATION:
200                 message->type = DHCP6_SOLICIT;
201
202                 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
203                                         sizeof(client->duid), &client->duid);
204                 if (r < 0)
205                         return r;
206
207                 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
208                 if (r < 0)
209                         return r;
210
211                 break;
212
213         case DHCP6_STATE_STOPPED:
214         case DHCP6_STATE_RS:
215                 return -EINVAL;
216         }
217
218         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
219                                           len - optlen);
220         if (r < 0)
221                 return r;
222
223         log_dhcp6_client(client, "Sent %s",
224                          dhcp6_message_type_to_string(message->type));
225
226         return 0;
227 }
228
229 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
230                                         void *userdata) {
231         sd_dhcp6_client *client = userdata;
232
233         assert(s);
234         assert(client);
235         assert(client->event);
236
237         client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
238
239         return 0;
240 }
241
242 static usec_t client_timeout_compute_random(usec_t val) {
243         return val - val / 10 +
244                 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
245 }
246
247 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
248                                  void *userdata) {
249         int r = 0;
250         sd_dhcp6_client *client = userdata;
251         usec_t time_now, init_retransmit_time, max_retransmit_time;
252         usec_t max_retransmit_duration;
253         uint8_t max_retransmit_count;
254         char time_string[FORMAT_TIMESPAN_MAX];
255
256         assert(s);
257         assert(client);
258         assert(client->event);
259
260         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
261
262         switch (client->state) {
263         case DHCP6_STATE_SOLICITATION:
264                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
265                 max_retransmit_time = DHCP6_SOL_MAX_RT;
266                 max_retransmit_count = 0;
267                 max_retransmit_duration = 0;
268
269                 break;
270
271         case DHCP6_STATE_STOPPED:
272         case DHCP6_STATE_RS:
273                 return 0;
274         }
275
276         if (max_retransmit_count &&
277             client->retransmit_count >= max_retransmit_count) {
278                 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
279                 return 0;
280         }
281
282         r = client_send_message(client);
283         if (r >= 0)
284                 client->retransmit_count++;
285
286
287         r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
288         if (r < 0)
289                 goto error;
290
291         if (!client->retransmit_time) {
292                 client->retransmit_time =
293                         client_timeout_compute_random(init_retransmit_time);
294
295                 if (client->state == DHCP6_STATE_SOLICITATION)
296                         client->retransmit_time += init_retransmit_time / 10;
297
298         } else {
299                 if (max_retransmit_time &&
300                     client->retransmit_time > max_retransmit_time / 2)
301                         client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
302                 else
303                         client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
304         }
305
306         log_dhcp6_client(client, "Next retransmission in %s",
307                          format_timespan(time_string, FORMAT_TIMESPAN_MAX,
308                                          client->retransmit_time, 0));
309
310         r = sd_event_add_time(client->event, &client->timeout_resend,
311                               CLOCK_MONOTONIC,
312                               time_now + client->retransmit_time,
313                               10 * USEC_PER_MSEC, client_timeout_resend,
314                               client);
315         if (r < 0)
316                 goto error;
317
318         r = sd_event_source_set_priority(client->timeout_resend,
319                                          client->event_priority);
320         if (r < 0)
321                 goto error;
322
323         if (max_retransmit_duration && !client->timeout_resend_expire) {
324
325                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
326                                  max_retransmit_duration / USEC_PER_SEC);
327
328                 r = sd_event_add_time(client->event,
329                                       &client->timeout_resend_expire,
330                                       CLOCK_MONOTONIC,
331                                       time_now + max_retransmit_duration,
332                                       USEC_PER_SEC,
333                                       client_timeout_resend_expire, client);
334                 if (r < 0)
335                         goto error;
336
337                 r = sd_event_source_set_priority(client->timeout_resend_expire,
338                                                  client->event_priority);
339                 if (r < 0)
340                         goto error;
341         }
342
343 error:
344         if (r < 0)
345                 client_stop(client, r);
346
347         return 0;
348 }
349
350 static int client_ensure_iaid(sd_dhcp6_client *client) {
351         const char *name = NULL;
352         uint64_t id;
353
354         assert(client);
355
356         if (client->ia_na.id)
357                 return 0;
358
359         if (detect_container(NULL) <= 0) {
360                 /* not in a container, udev will be around */
361                 _cleanup_udev_unref_ struct udev *udev;
362                 _cleanup_udev_device_unref_ struct udev_device *device;
363                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
364
365                 udev = udev_new();
366                 if (!udev)
367                         return -ENOMEM;
368
369                 sprintf(ifindex_str, "n%d", client->index);
370                 device = udev_device_new_from_device_id(udev, ifindex_str);
371                 if (!device)
372                         return -errno;
373
374                 if (udev_device_get_is_initialized(device) <= 0)
375                         /* not yet ready */
376                         return -EBUSY;
377
378                 name = net_get_name(device);
379         }
380
381         if (name)
382                 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
383         else
384                 /* fall back to mac address if no predictable name available */
385                 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
386                           HASH_KEY.bytes);
387
388         /* fold into 32 bits */
389         client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
390
391         return 0;
392 }
393
394 static int client_parse_message(sd_dhcp6_client *client,
395                                 DHCP6Message *message, size_t len,
396                                 sd_dhcp6_lease *lease) {
397         int r;
398         uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
399         uint16_t optcode, status;
400         size_t optlen, id_len;
401         bool clientid = false;
402         be32_t iaid_lease;
403
404         while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
405                                        &optval)) >= 0) {
406                 switch (optcode) {
407                 case DHCP6_OPTION_CLIENTID:
408                         if (clientid) {
409                                 log_dhcp6_client(client, "%s contains multiple clientids",
410                                                  dhcp6_message_type_to_string(message->type));
411                                 return -EINVAL;
412                         }
413
414                         if (optlen != sizeof(client->duid) ||
415                             memcmp(&client->duid, optval, optlen) != 0) {
416                                 log_dhcp6_client(client, "%s DUID does not match",
417                                                  dhcp6_message_type_to_string(message->type));
418
419                                 return -EINVAL;
420                         }
421                         clientid = true;
422
423                         break;
424
425                 case DHCP6_OPTION_SERVERID:
426                         r = dhcp6_lease_get_serverid(lease, &id, &id_len);
427                         if (r >= 0 && id) {
428                                 log_dhcp6_client(client, "%s contains multiple serverids",
429                                                  dhcp6_message_type_to_string(message->type));
430                                 return -EINVAL;
431                         }
432
433                         r = dhcp6_lease_set_serverid(lease, optval, optlen);
434                         if (r < 0)
435                                 return r;
436
437                         break;
438
439                 case DHCP6_OPTION_PREFERENCE:
440                         if (optlen != 1)
441                                 return -EINVAL;
442
443                         r = dhcp6_lease_set_preference(lease, *optval);
444                         if (r < 0)
445                                 return r;
446
447                         break;
448
449                 case DHCP6_OPTION_STATUS_CODE:
450                         if (optlen < 2)
451                                 return -EINVAL;
452
453                         status = optval[0] << 8 | optval[1];
454                         if (status) {
455                                 log_dhcp6_client(client, "%s Status %s",
456                                                  dhcp6_message_type_to_string(message->type),
457                                                  dhcp6_message_status_to_string(status));
458                                 return -EINVAL;
459                         }
460
461                         break;
462
463                 case DHCP6_OPTION_IA_NA:
464                         r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
465                                                   &lease->ia);
466                         if (r < 0 && r != -ENOMSG)
467                                 return r;
468
469                         r = dhcp6_lease_get_iaid(lease, &iaid_lease);
470                         if (r < 0)
471                                 return r;
472
473                         if (client->ia_na.id != iaid_lease) {
474                                 log_dhcp6_client(client, "%s has wrong IAID",
475                                                  dhcp6_message_type_to_string(message->type));
476                                 return -EINVAL;
477                         }
478
479                         break;
480                 }
481         }
482
483         if ((r < 0 && r != -ENOMSG) || !clientid) {
484                 log_dhcp6_client(client, "%s has incomplete options",
485                                  dhcp6_message_type_to_string(message->type));
486                 return -EINVAL;
487         }
488
489         r = dhcp6_lease_get_serverid(lease, &id, &id_len);
490         if (r < 0)
491                 log_dhcp6_client(client, "%s has no server id",
492                                  dhcp6_message_type_to_string(message->type));
493
494         return r;
495 }
496
497 static int client_receive_advertise(sd_dhcp6_client *client,
498                                     DHCP6Message *advertise, size_t len) {
499         int r;
500         _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
501         uint8_t pref_advertise = 0, pref_lease = 0;
502
503         if (advertise->type != DHCP6_ADVERTISE)
504                 return -EINVAL;
505
506         r = dhcp6_lease_new(&lease);
507         if (r < 0)
508                 return r;
509
510         r = client_parse_message(client, advertise, len, lease);
511         if (r < 0)
512                 return r;
513
514         r = dhcp6_lease_get_preference(lease, &pref_advertise);
515         if (r < 0)
516                 return r;
517
518         r = dhcp6_lease_get_preference(client->lease, &pref_lease);
519         if (!client->lease || r < 0 || pref_advertise > pref_lease) {
520                 sd_dhcp6_lease_unref(client->lease);
521                 client->lease = lease;
522                 lease = NULL;
523                 r = 0;
524         }
525
526         return r;
527 }
528
529 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
530                                   void *userdata) {
531         sd_dhcp6_client *client = userdata;
532         _cleanup_free_ DHCP6Message *message;
533         int r, buflen, len;
534
535         assert(s);
536         assert(client);
537         assert(client->event);
538
539         r = ioctl(fd, FIONREAD, &buflen);
540         if (r < 0 || buflen <= 0)
541                 buflen = DHCP6_MIN_OPTIONS_SIZE;
542
543         message = malloc0(buflen);
544         if (!message)
545                 return -ENOMEM;
546
547         len = read(fd, message, buflen);
548         if ((size_t)len < sizeof(DHCP6Message)) {
549                 log_dhcp6_client(client, "could not receive message from UDP socket: %s", strerror(errno));
550                 return 0;
551         }
552
553         switch(message->type) {
554         case DHCP6_SOLICIT:
555         case DHCP6_REQUEST:
556         case DHCP6_CONFIRM:
557         case DHCP6_RENEW:
558         case DHCP6_REBIND:
559         case DHCP6_RELEASE:
560         case DHCP6_DECLINE:
561         case DHCP6_INFORMATION_REQUEST:
562         case DHCP6_RELAY_FORW:
563         case DHCP6_RELAY_REPL:
564                 return 0;
565
566         case DHCP6_ADVERTISE:
567         case DHCP6_REPLY:
568         case DHCP6_RECONFIGURE:
569                 break;
570
571         default:
572                 log_dhcp6_client(client, "unknown message type %d",
573                                  message->type);
574                 return 0;
575         }
576
577         if (client->transaction_id != (message->transaction_id &
578                                        htobe32(0x00ffffff)))
579                 return 0;
580
581         switch (client->state) {
582         case DHCP6_STATE_SOLICITATION:
583                 r = client_receive_advertise(client, message, len);
584
585                 break;
586
587         case DHCP6_STATE_STOPPED:
588         case DHCP6_STATE_RS:
589                 return 0;
590         }
591
592         if (r >= 0) {
593                 log_dhcp6_client(client, "Recv %s",
594                                  dhcp6_message_type_to_string(message->type));
595         }
596
597         return 0;
598 }
599
600 static int client_start(sd_dhcp6_client *client)
601 {
602         int r;
603
604         assert_return(client, -EINVAL);
605         assert_return(client->event, -EINVAL);
606         assert_return(client->index > 0, -EINVAL);
607
608         r = client_ensure_iaid(client);
609         if (r < 0)
610                 return r;
611
612         r = dhcp6_network_bind_udp_socket(client->index, NULL);
613         if (r < 0)
614                 return r;
615
616         client->fd = r;
617
618         r = sd_event_add_io(client->event, &client->receive_message,
619                             client->fd, EPOLLIN, client_receive_message,
620                             client);
621         if (r < 0)
622                 return r;
623
624         r = sd_event_source_set_priority(client->receive_message,
625                                          client->event_priority);
626         if (r < 0)
627                 return r;
628
629         client->state = DHCP6_STATE_SOLICITATION;
630
631         r = sd_event_add_time(client->event, &client->timeout_resend,
632                               CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
633                               client);
634         if (r < 0)
635                 return r;
636
637         r = sd_event_source_set_priority(client->timeout_resend,
638                                          client->event_priority);
639         if (r < 0)
640                 return r;
641
642         return 0;
643 }
644
645 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
646 {
647         client_stop(client, DHCP6_EVENT_STOP);
648
649         return 0;
650 }
651
652 int sd_dhcp6_client_start(sd_dhcp6_client *client)
653 {
654         int r = 0;
655
656         assert_return(client, -EINVAL);
657         assert_return(client->event, -EINVAL);
658         assert_return(client->index > 0, -EINVAL);
659
660         r = client_initialize(client);
661         if (r < 0)
662                 return r;
663
664         return client_start(client);
665 }
666
667 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
668                                  int priority)
669 {
670         int r;
671
672         assert_return(client, -EINVAL);
673         assert_return(!client->event, -EBUSY);
674
675         if (event)
676                 client->event = sd_event_ref(event);
677         else {
678                 r = sd_event_default(&client->event);
679                 if (r < 0)
680                         return 0;
681         }
682
683         client->event_priority = priority;
684
685         return 0;
686 }
687
688 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
689         assert_return(client, -EINVAL);
690
691         client->event = sd_event_unref(client->event);
692
693         return 0;
694 }
695
696 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
697         if (!client)
698                 return NULL;
699
700         return client->event;
701 }
702
703 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
704         if (client)
705                 assert_se(REFCNT_INC(client->n_ref) >= 2);
706
707         return client;
708 }
709
710 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
711         if (client && REFCNT_DEC(client->n_ref) <= 0) {
712                 client_initialize(client);
713
714                 sd_dhcp6_client_detach_event(client);
715
716                 free(client);
717
718                 return NULL;
719         }
720
721         return client;
722 }
723
724 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
725 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
726
727 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
728 {
729         _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
730         sd_id128_t machine_id;
731         int r;
732
733         assert_return(ret, -EINVAL);
734
735         client = new0(sd_dhcp6_client, 1);
736         if (!client)
737                 return -ENOMEM;
738
739         client->n_ref = REFCNT_INIT;
740
741         client->ia_na.type = DHCP6_OPTION_IA_NA;
742
743         client->index = -1;
744
745         /* initialize DUID */
746         client->duid.type = htobe16(DHCP6_DUID_EN);
747         client->duid.pen = htobe32(SYSTEMD_PEN);
748
749         r = sd_id128_get_machine(&machine_id);
750         if (r < 0)
751                 return r;
752
753         /* a bit of snake-oil perhaps, but no need to expose the machine-id
754            directly */
755         siphash24(client->duid.id, &machine_id, sizeof(machine_id),
756                   HASH_KEY.bytes);
757
758         *ret = client;
759         client = NULL;
760
761         return 0;
762 }