summary refs log tree commit diff
path: root/sysdeps/unix/sysv/linux/ifaddrs.c
blob: 150ec8a9b02a7021d57f2d615c43365fb334d3b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
/* getifaddrs -- get names and addresses of all network interfaces
   Copyright (C) 2003-2007, 2008 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <alloca.h>
#include <assert.h>
#include <errno.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netpacket/packet.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sysdep.h>
#include <time.h>
#include <unistd.h>
#include <kernel-features.h>

#include "netlinkaccess.h"


/* We don't know if we have NETLINK support compiled in in our
   Kernel, so include the old implementation as fallback.  */
#if __ASSUME_NETLINK_SUPPORT == 0
int __no_netlink_support attribute_hidden;

# define getifaddrs fallback_getifaddrs
# include "sysdeps/gnu/ifaddrs.c"
# undef getifaddrs
#endif


/* There is a problem with this type.  The address length for
   Infiniband sockets is much longer than the 8 bytes allocated in the
   sockaddr_ll definition.  Hence we use here a special
   definition.  */
struct sockaddr_ll_max
  {
    unsigned short int sll_family;
    unsigned short int sll_protocol;
    int sll_ifindex;
    unsigned short int sll_hatype;
    unsigned char sll_pkttype;
    unsigned char sll_halen;
    unsigned char sll_addr[24];
  };


/* struct to hold the data for one ifaddrs entry, so we can allocate
   everything at once.  */
struct ifaddrs_storage
{
  struct ifaddrs ifa;
  union
  {
    /* Save space for the biggest of the four used sockaddr types and
       avoid a lot of casts.  */
    struct sockaddr sa;
    struct sockaddr_ll_max sl;
    struct sockaddr_in s4;
    struct sockaddr_in6 s6;
  } addr, netmask, broadaddr;
  char name[IF_NAMESIZE + 1];
};


void
__netlink_free_handle (struct netlink_handle *h)
{
  struct netlink_res *ptr;
  int saved_errno = errno;

  ptr = h->nlm_list;
  while (ptr != NULL)
    {
      struct netlink_res *tmpptr;

      tmpptr = ptr->next;
      free (ptr);
      ptr = tmpptr;
    }

  __set_errno (saved_errno);
}


static int
__netlink_sendreq (struct netlink_handle *h, int type)
{
  struct req
  {
    struct nlmsghdr nlh;
    struct rtgenmsg g;
    char pad[0];
  } req;
  struct sockaddr_nl nladdr;

  if (h->seq == 0)
    h->seq = time (NULL);

  req.nlh.nlmsg_len = sizeof (req);
  req.nlh.nlmsg_type = type;
  req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
  req.nlh.nlmsg_pid = 0;
  req.nlh.nlmsg_seq = h->seq;
  req.g.rtgen_family = AF_UNSPEC;
  if (sizeof (req) != offsetof (struct req, pad))
    memset (req.pad, '\0', sizeof (req) - offsetof (struct req, pad));

  memset (&nladdr, '\0', sizeof (nladdr));
  nladdr.nl_family = AF_NETLINK;

  return TEMP_FAILURE_RETRY (__sendto (h->fd, (void *) &req, sizeof (req), 0,
				       (struct sockaddr *) &nladdr,
				       sizeof (nladdr)));
}


int
__netlink_request (struct netlink_handle *h, int type)
{
  struct netlink_res *nlm_next;
  struct sockaddr_nl nladdr;
  struct nlmsghdr *nlmh;
  ssize_t read_len;
  bool done = false;

#ifdef PAGE_SIZE
  /* Help the compiler optimize out the malloc call if PAGE_SIZE
     is constant and smaller or equal to PTHREAD_STACK_MIN/4.  */
  const size_t buf_size = PAGE_SIZE;
#else
  const size_t buf_size = __getpagesize ();
#endif
  bool use_malloc = false;
  char *buf;

  if (__libc_use_alloca (buf_size))
    buf = alloca (buf_size);
  else
    {
      buf = malloc (buf_size);
      if (buf != NULL)
	use_malloc = true;
      else
	goto out_fail;
    }

  struct iovec iov = { buf, buf_size };

  if (__netlink_sendreq (h, type) < 0)
    goto out_fail;

  while (! done)
    {
      struct msghdr msg =
	{
	  (void *) &nladdr, sizeof (nladdr),
	  &iov, 1,
	  NULL, 0,
	  0
	};

      read_len = TEMP_FAILURE_RETRY (__recvmsg (h->fd, &msg, 0));
      if (read_len < 0)
	goto out_fail;

      if (nladdr.nl_pid != 0)
	continue;

      if (__builtin_expect (msg.msg_flags & MSG_TRUNC, 0))
	goto out_fail;

      size_t count = 0;
      size_t remaining_len = read_len;
      for (nlmh = (struct nlmsghdr *) buf;
	   NLMSG_OK (nlmh, remaining_len);
	   nlmh = (struct nlmsghdr *) NLMSG_NEXT (nlmh, remaining_len))
	{
	  if ((pid_t) nlmh->nlmsg_pid != h->pid
	      || nlmh->nlmsg_seq != h->seq)
	    continue;

	  ++count;
	  if (nlmh->nlmsg_type == NLMSG_DONE)
	    {
	      /* We found the end, leave the loop.  */
	      done = true;
	      break;
	    }
	  if (nlmh->nlmsg_type == NLMSG_ERROR)
	    {
	      struct nlmsgerr *nlerr = (struct nlmsgerr *) NLMSG_DATA (nlmh);
	      if (nlmh->nlmsg_len < NLMSG_LENGTH (sizeof (struct nlmsgerr)))
		errno = EIO;
	      else
		errno = -nlerr->error;
	      goto out_fail;
	    }
	}

      /* If there was nothing with the expected nlmsg_pid and nlmsg_seq,
	 there is no point to record it.  */
      if (count == 0)
	continue;

      nlm_next = (struct netlink_res *) malloc (sizeof (struct netlink_res)
						+ read_len);
      if (nlm_next == NULL)
	goto out_fail;
      nlm_next->next = NULL;
      nlm_next->nlh = memcpy (nlm_next + 1, buf, read_len);
      nlm_next->size = read_len;
      nlm_next->seq = h->seq;
      if (h->nlm_list == NULL)
	h->nlm_list = nlm_next;
      else
	h->end_ptr->next = nlm_next;
      h->end_ptr = nlm_next;
    }

  if (use_malloc)
    free (buf);
  return 0;

out_fail:
  if (use_malloc)
    free (buf);
  return -1;
}


void
__netlink_close (struct netlink_handle *h)
{
  /* Don't modify errno.  */
  INTERNAL_SYSCALL_DECL (err);
  (void) INTERNAL_SYSCALL (close, err, 1, h->fd);
}


/* Open a NETLINK socket.  */
int
__netlink_open (struct netlink_handle *h)
{
  struct sockaddr_nl nladdr;

  h->fd = __socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if (h->fd < 0)
    goto out;

  memset (&nladdr, '\0', sizeof (nladdr));
  nladdr.nl_family = AF_NETLINK;
  if (__bind (h->fd, (struct sockaddr *) &nladdr, sizeof (nladdr)) < 0)
    {
    close_and_out:
      __netlink_close (h);
    out:
#if __ASSUME_NETLINK_SUPPORT == 0
      __no_netlink_support = 1;
#endif
      return -1;
    }
  /* Determine the ID the kernel assigned for this netlink connection.
     It is not necessarily the PID if there is more than one socket
     open.  */
  socklen_t addr_len = sizeof (nladdr);
  if (__getsockname (h->fd, (struct sockaddr *) &nladdr, &addr_len) < 0)
    goto close_and_out;
  h->pid = nladdr.nl_pid;
  return 0;
}


/* We know the number of RTM_NEWLINK entries, so we reserve the first
   # of entries for this type. All RTM_NEWADDR entries have an index
   pointer to the RTM_NEWLINK entry.  To find the entry, create
   a table to map kernel index entries to our index numbers.
   Since we get at first all RTM_NEWLINK entries, it can never happen
   that a RTM_NEWADDR index is not known to this map.  */
static int
internal_function
map_newlink (int index, struct ifaddrs_storage *ifas, int *map, int max)
{
  int i;

  for (i = 0; i < max; i++)
    {
      if (map[i] == -1)
	{
	  map[i] = index;
	  if (i > 0)
	    ifas[i - 1].ifa.ifa_next = &ifas[i].ifa;
	  return i;
	}
      else if (map[i] == index)
	return i;
    }
  /* This should never be reached. If this will be reached, we have
     a very big problem.  */
  abort ();
}


/* Create a linked list of `struct ifaddrs' structures, one for each
   network interface on the host machine.  If successful, store the
   list in *IFAP and 2004, 2005, 2006, return 0.  On errors, return -1 and set `errno'.  */
int
getifaddrs (struct ifaddrs **ifap)
{
  struct netlink_handle nh = { 0, 0, 0, NULL, NULL };
  struct netlink_res *nlp;
  struct ifaddrs_storage *ifas;
  unsigned int i, newlink, newaddr, newaddr_idx;
  int *map_newlink_data;
  size_t ifa_data_size = 0;  /* Size to allocate for all ifa_data.  */
  char *ifa_data_ptr;	/* Pointer to the unused part of memory for
				ifa_data.  */
  int result = 0;

  *ifap = NULL;

  if (! __no_netlink_support && __netlink_open (&nh) < 0)
    {
#if __ASSUME_NETLINK_SUPPORT != 0
      return -1;
#endif
    }

#if __ASSUME_NETLINK_SUPPORT == 0
  if (__no_netlink_support)
    return fallback_getifaddrs (ifap);
#endif

  /* Tell the kernel that we wish to get a list of all
     active interfaces, collect all data for every interface.  */
  if (__netlink_request (&nh, RTM_GETLINK) < 0)
    {
      result = -1;
      goto exit_free;
    }

  /* Now ask the kernel for all addresses which are assigned
     to an interface and collect all data for every interface.
     Since we store the addresses after the interfaces in the
     list, we will later always find the interface before the
     corresponding addresses.  */
  ++nh.seq;
  if (__netlink_request (&nh, RTM_GETADDR) < 0)
    {
      result = -1;
      goto exit_free;
    }

  /* Count all RTM_NEWLINK and RTM_NEWADDR entries to allocate
     enough memory.  */
  newlink = newaddr = 0;
  for (nlp = nh.nlm_list; nlp; nlp = nlp->next)
    {
      struct nlmsghdr *nlh;
      size_t size = nlp->size;

      if (nlp->nlh == NULL)
	continue;

      /* Walk through all entries we got from the kernel and look, which
	 message type they contain.  */
      for (nlh = nlp->nlh; NLMSG_OK (nlh, size); nlh = NLMSG_NEXT (nlh, size))
	{
	  /* Check if the message is what we want.  */
	  if ((pid_t) nlh->nlmsg_pid != nh.pid || nlh->nlmsg_seq != nlp->seq)
	    continue;

	  if (nlh->nlmsg_type == NLMSG_DONE)
	    break;		/* ok */

	  if (nlh->nlmsg_type == RTM_NEWLINK)
	    {
	      /* A RTM_NEWLINK message can have IFLA_STATS data. We need to
		 know the size before creating the list to allocate enough
		 memory.  */
	      struct ifinfomsg *ifim = (struct ifinfomsg *) NLMSG_DATA (nlh);
	      struct rtattr *rta = IFLA_RTA (ifim);
	      size_t rtasize = IFLA_PAYLOAD (nlh);

	      while (RTA_OK (rta, rtasize))
		{
		  size_t rta_payload = RTA_PAYLOAD (rta);

		  if (rta->rta_type == IFLA_STATS)
		    {
		      ifa_data_size += rta_payload;
		      break;
		    }
		  else
		    rta = RTA_NEXT (rta, rtasize);
		}
	      ++newlink;
	    }
	  else if (nlh->nlmsg_type == RTM_NEWADDR)
	    ++newaddr;
	}
    }

  /* Return if no interface is up.  */
  if ((newlink + newaddr) == 0)
    goto exit_free;

  /* Allocate memory for all entries we have and initialize next
     pointer.  */
  ifas = (struct ifaddrs_storage *) calloc (1,
					    (newlink + newaddr)
					    * sizeof (struct ifaddrs_storage)
					    + ifa_data_size);
  if (ifas == NULL)
    {
      result = -1;
      goto exit_free;
    }

  /* Table for mapping kernel index to entry in our list.  */
  map_newlink_data = alloca (newlink * sizeof (int));
  memset (map_newlink_data, '\xff', newlink * sizeof (int));

  ifa_data_ptr = (char *) &ifas[newlink + newaddr];
  newaddr_idx = 0;		/* Counter for newaddr index.  */

  /* Walk through the list of data we got from the kernel.  */
  for (nlp = nh.nlm_list; nlp; nlp = nlp->next)
    {
      struct nlmsghdr *nlh;
      size_t size = nlp->size;

      if (nlp->nlh == NULL)
	continue;

      /* Walk through one message and look at the type: If it is our
	 message, we need RTM_NEWLINK/RTM_NEWADDR and stop if we reach
	 the end or we find the end marker (in this case we ignore the
	 following data.  */
      for (nlh = nlp->nlh; NLMSG_OK (nlh, size); nlh = NLMSG_NEXT (nlh, size))
	{
	  int ifa_index = 0;

	  /* Check if the message is the one we want */
	  if ((pid_t) nlh->nlmsg_pid != nh.pid || nlh->nlmsg_seq != nlp->seq)
	    continue;

	  if (nlh->nlmsg_type == NLMSG_DONE)
	    break;		/* ok */

	  if (nlh->nlmsg_type == RTM_NEWLINK)
	    {
	      /* We found a new interface. Now extract everything from the
		 interface data we got and need.  */
	      struct ifinfomsg *ifim = (struct ifinfomsg *) NLMSG_DATA (nlh);
	      struct rtattr *rta = IFLA_RTA (ifim);
	      size_t rtasize = IFLA_PAYLOAD (nlh);

	      /* Interfaces are stored in the first "newlink" entries
		 of our list, starting in the order as we got from the
		 kernel.  */
	      ifa_index = map_newlink (ifim->ifi_index - 1, ifas,
				       map_newlink_data, newlink);
	      ifas[ifa_index].ifa.ifa_flags = ifim->ifi_flags;

	      while (RTA_OK (rta, rtasize))
		{
		  char *rta_data = RTA_DATA (rta);
		  size_t rta_payload = RTA_PAYLOAD (rta);

		  switch (rta->rta_type)
		    {
		    case IFLA_ADDRESS:
		      if (rta_payload <= sizeof (ifas[ifa_index].addr))
			{
			  ifas[ifa_index].addr.sl.sll_family = AF_PACKET;
			  memcpy (ifas[ifa_index].addr.sl.sll_addr,
				  (char *) rta_data, rta_payload);
			  ifas[ifa_index].addr.sl.sll_halen = rta_payload;
			  ifas[ifa_index].addr.sl.sll_ifindex
			    = ifim->ifi_index;
			  ifas[ifa_index].addr.sl.sll_hatype = ifim->ifi_type;

			  ifas[ifa_index].ifa.ifa_addr
			    = &ifas[ifa_index].addr.sa;
			}
		      break;

		    case IFLA_BROADCAST:
		      if (rta_payload <= sizeof (ifas[ifa_index].broadaddr))
			{
			  ifas[ifa_index].broadaddr.sl.sll_family = AF_PACKET;
			  memcpy (ifas[ifa_index].broadaddr.sl.sll_addr,
				  (char *) rta_data, rta_payload);
			  ifas[ifa_index].broadaddr.sl.sll_halen = rta_payload;
			  ifas[ifa_index].broadaddr.sl.sll_ifindex
			    = ifim->ifi_index;
			  ifas[ifa_index].broadaddr.sl.sll_hatype
			    = ifim->ifi_type;

			  ifas[ifa_index].ifa.ifa_broadaddr
			    = &ifas[ifa_index].broadaddr.sa;
			}
		      break;

		    case IFLA_IFNAME:	/* Name of Interface */
		      if ((rta_payload + 1) <= sizeof (ifas[ifa_index].name))
			{
			  ifas[ifa_index].ifa.ifa_name = ifas[ifa_index].name;
			  *(char *) __mempcpy (ifas[ifa_index].name, rta_data,
					       rta_payload) = '\0';
			}
		      break;

		    case IFLA_STATS:	/* Statistics of Interface */
		      ifas[ifa_index].ifa.ifa_data = ifa_data_ptr;
		      ifa_data_ptr += rta_payload;
		      memcpy (ifas[ifa_index].ifa.ifa_data, rta_data,
			      rta_payload);
		      break;

		    case IFLA_UNSPEC:
		      break;
		    case IFLA_MTU:
		      break;
		    case IFLA_LINK:
		      break;
		    case IFLA_QDISC:
		      break;
		    default:
		      break;
		    }

		  rta = RTA_NEXT (rta, rtasize);
		}
	    }
	  else if (nlh->nlmsg_type == RTM_NEWADDR)
	    {
	      struct ifaddrmsg *ifam = (struct ifaddrmsg *) NLMSG_DATA (nlh);
	      struct rtattr *rta = IFA_RTA (ifam);
	      size_t rtasize = IFA_PAYLOAD (nlh);

	      /* New Addresses are stored in the order we got them from
		 the kernel after the interfaces. Theoretically it is possible
		 that we have holes in the interface part of the list,
		 but we always have already the interface for this address.  */
	      ifa_index = newlink + newaddr_idx;
	      ifas[ifa_index].ifa.ifa_flags
		= ifas[map_newlink (ifam->ifa_index - 1, ifas,
				    map_newlink_data, newlink)].ifa.ifa_flags;
	      if (ifa_index > 0)
		ifas[ifa_index - 1].ifa.ifa_next = &ifas[ifa_index].ifa;
	      ++newaddr_idx;

	      while (RTA_OK (rta, rtasize))
		{
		  char *rta_data = RTA_DATA (rta);
		  size_t rta_payload = RTA_PAYLOAD (rta);

		  switch (rta->rta_type)
		    {
		    case IFA_ADDRESS:
		      {
			struct sockaddr *sa;

			if (ifas[ifa_index].ifa.ifa_addr != NULL)
			  {
			    /* In a point-to-poing network IFA_ADDRESS
			       contains the destination address, local
			       address is supplied in IFA_LOCAL attribute.
			       destination address and broadcast address
			       are stored in an union, so it doesn't matter
			       which name we use.  */
			    ifas[ifa_index].ifa.ifa_broadaddr
			      = &ifas[ifa_index].broadaddr.sa;
			    sa = &ifas[ifa_index].broadaddr.sa;
			  }
			else
			  {
			    ifas[ifa_index].ifa.ifa_addr
			      = &ifas[ifa_index].addr.sa;
			    sa = &ifas[ifa_index].addr.sa;
			  }

			sa->sa_family = ifam->ifa_family;

			switch (ifam->ifa_family)
			  {
			  case AF_INET:
			    /* Size must match that of an address for IPv4.  */
			    if (rta_payload == 4)
			      memcpy (&((struct sockaddr_in *) sa)->sin_addr,
				      rta_data, rta_payload);
			    break;

			  case AF_INET6:
			    /* Size must match that of an address for IPv6.  */
			    if (rta_payload == 16)
			      {
				memcpy (&((struct sockaddr_in6 *) sa)->sin6_addr,
					rta_data, rta_payload);
				if (IN6_IS_ADDR_LINKLOCAL (rta_data)
				    || IN6_IS_ADDR_MC_LINKLOCAL (rta_data))
				  ((struct sockaddr_in6 *) sa)->sin6_scope_id
				    = ifam->ifa_index;
			      }
			    break;

			  default:
			    if (rta_payload <= sizeof (ifas[ifa_index].addr))
			      memcpy (sa->sa_data, rta_data, rta_payload);
			    break;
			  }
		      }
		      break;

		    case IFA_LOCAL:
		      if (ifas[ifa_index].ifa.ifa_addr != NULL)
			{
			  /* If ifa_addr is set and we get IFA_LOCAL,
			     assume we have a point-to-point network.
			     Move address to correct field.  */
			  ifas[ifa_index].broadaddr = ifas[ifa_index].addr;
			  ifas[ifa_index].ifa.ifa_broadaddr
			    = &ifas[ifa_index].broadaddr.sa;
			  memset (&ifas[ifa_index].addr, '\0',
				  sizeof (ifas[ifa_index].addr));
			}

		      ifas[ifa_index].ifa.ifa_addr = &ifas[ifa_index].addr.sa;
		      ifas[ifa_index].ifa.ifa_addr->sa_family
			= ifam->ifa_family;

		      switch (ifam->ifa_family)
			{
			case AF_INET:
			  /* Size must match that of an address for IPv4.  */
			  if (rta_payload == 4)
			    memcpy (&ifas[ifa_index].addr.s4.sin_addr,
				  rta_data, rta_payload);
			  break;

			case AF_INET6:
			  /* Size must match that of an address for IPv6.  */
			  if (rta_payload == 16)
			    {
			      memcpy (&ifas[ifa_index].addr.s6.sin6_addr,
				      rta_data, rta_payload);
			      if (IN6_IS_ADDR_LINKLOCAL (rta_data)
				  || IN6_IS_ADDR_MC_LINKLOCAL (rta_data))
				ifas[ifa_index].addr.s6.sin6_scope_id =
				  ifam->ifa_index;
			    }
			  break;

			default:
			  if (rta_payload <= sizeof (ifas[ifa_index].addr))
			    memcpy (ifas[ifa_index].addr.sa.sa_data,
				    rta_data, rta_payload);
			  break;
			}
		      break;

		    case IFA_BROADCAST:
		      /* We get IFA_BROADCAST, so IFA_LOCAL was too much.  */
		      if (ifas[ifa_index].ifa.ifa_broadaddr != NULL)
			memset (&ifas[ifa_index].broadaddr, '\0',
				sizeof (ifas[ifa_index].broadaddr));

		      ifas[ifa_index].ifa.ifa_broadaddr
			= &ifas[ifa_index].broadaddr.sa;
		      ifas[ifa_index].ifa.ifa_broadaddr->sa_family
			= ifam->ifa_family;

		      switch (ifam->ifa_family)
			{
			case AF_INET:
			  /* Size must match that of an address for IPv4.  */
			  if (rta_payload == 4)
			    memcpy (&ifas[ifa_index].broadaddr.s4.sin_addr,
				    rta_data, rta_payload);
			  break;

			case AF_INET6:
			  /* Size must match that of an address for IPv6.  */
			  if (rta_payload == 16)
			    {
			      memcpy (&ifas[ifa_index].broadaddr.s6.sin6_addr,
				      rta_data, rta_payload);
			      if (IN6_IS_ADDR_LINKLOCAL (rta_data)
				  || IN6_IS_ADDR_MC_LINKLOCAL (rta_data))
				ifas[ifa_index].broadaddr.s6.sin6_scope_id
				  = ifam->ifa_index;
			    }
			  break;

			default:
			  if (rta_payload <= sizeof (ifas[ifa_index].addr))
			    memcpy (&ifas[ifa_index].broadaddr.sa.sa_data,
				    rta_data, rta_payload);
			  break;
			}
		      break;

		    case IFA_LABEL:
		      if (rta_payload + 1 <= sizeof (ifas[ifa_index].name))
			{
			  ifas[ifa_index].ifa.ifa_name = ifas[ifa_index].name;
			  *(char *) __mempcpy (ifas[ifa_index].name, rta_data,
					       rta_payload) = '\0';
			}
		      else
			abort ();
		      break;

		    case IFA_UNSPEC:
		      break;
		    case IFA_CACHEINFO:
		      break;
		    default:
		      break;
		    }

		  rta = RTA_NEXT (rta, rtasize);
		}

	      /* If we didn't get the interface name with the
		 address, use the name from the interface entry.  */
	      if (ifas[ifa_index].ifa.ifa_name == NULL)
		ifas[ifa_index].ifa.ifa_name
		  = ifas[map_newlink (ifam->ifa_index - 1, ifas,
				      map_newlink_data, newlink)].ifa.ifa_name;

	      /* Calculate the netmask.  */
	      if (ifas[ifa_index].ifa.ifa_addr
		  && ifas[ifa_index].ifa.ifa_addr->sa_family != AF_UNSPEC
		  && ifas[ifa_index].ifa.ifa_addr->sa_family != AF_PACKET)
		{
		  uint32_t max_prefixlen = 0;
		  char *cp = NULL;

		  ifas[ifa_index].ifa.ifa_netmask
		    = &ifas[ifa_index].netmask.sa;

		  switch (ifas[ifa_index].ifa.ifa_addr->sa_family)
		    {
		    case AF_INET:
		      cp = (char *) &ifas[ifa_index].netmask.s4.sin_addr;
		      max_prefixlen = 32;
		      break;

		    case AF_INET6:
		      cp = (char *) &ifas[ifa_index].netmask.s6.sin6_addr;
		      max_prefixlen = 128;
		      break;
		    }

		  ifas[ifa_index].ifa.ifa_netmask->sa_family
		    = ifas[ifa_index].ifa.ifa_addr->sa_family;

		  if (cp != NULL)
		    {
		      char c;
		      unsigned int preflen;

		      if ((max_prefixlen > 0) &&
			  (ifam->ifa_prefixlen > max_prefixlen))
			preflen = max_prefixlen;
		      else
			preflen = ifam->ifa_prefixlen;

		      for (i = 0; i < (preflen / 8); i++)
			*cp++ = 0xff;
		      c = 0xff;
		      c <<= (8 - (preflen % 8));
		      *cp = c;
		    }
		}
	    }
	}
    }

  assert (ifa_data_ptr <= (char *) &ifas[newlink + newaddr] + ifa_data_size);

  if (newaddr_idx > 0)
    {
      for (i = 0; i < newlink; ++i)
	if (map_newlink_data[i] == -1)
	  {
	    /* We have fewer links then we anticipated.  Adjust the
	       forward pointer to the first address entry.  */
	    ifas[i - 1].ifa.ifa_next = &ifas[newlink].ifa;
	  }

      if (i == 0 && newlink > 0)
	/* No valid link, but we allocated memory.  We have to
	   populate the first entry.  */
	memmove (ifas, &ifas[newlink], sizeof (struct ifaddrs_storage));
    }

  *ifap = &ifas[0].ifa;

 exit_free:
  __netlink_free_handle (&nh);
  __netlink_close (&nh);

  return result;
}
libc_hidden_def (getifaddrs)


#if __ASSUME_NETLINK_SUPPORT != 0
void
freeifaddrs (struct ifaddrs *ifa)
{
  free (ifa);
}
libc_hidden_def (freeifaddrs)
#endif