1 /*******************************************************************************
2 
3     Functions to get IP address from the given interface.
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.sys.GetIfAddrs;
17 
18 
19 import ocean.transition;
20 
21 import core.stdc.errno;
22 import core.stdc.string;
23 import ocean.stdc.posix.arpa.inet;
24 import ocean.stdc.posix.netinet.in_: sockaddr_in, sockaddr_in6;
25 import ocean.stdc.posix.sys.socket;
26 
27 static if (__VERSION__ >= 2000 && __VERSION__ < 2073)
28 {
29     extern (C)
30     {
31         struct ifaddrs
32         {
33             /// Next item in the list
34             ifaddrs*         ifa_next;
35             /// Name of the interface
36             char*            ifa_name;
37             /// Flags from SIOCGIFFLAGS
38             uint      ifa_flags;
39             /// Address of interface
40             sockaddr* ifa_addr;
41             /// Netmask of interface
42             sockaddr* ifa_netmask;
43 
44             union
45             {
46                 /// Broadcast address of the interface
47                 sockaddr* ifu_broadaddr;
48                 /// Point-to-point destination addresss
49                 sockaddr* if_dstaddr;
50             }
51 
52             /// Address specific data
53             void* ifa_data;
54         };
55 
56         int getifaddrs (ifaddrs**);
57 
58         void freeifaddrs (ifaddrs*);
59     }
60 }
61 else
62     import core.sys.linux.ifaddrs;
63 
64 import core.sys.posix.sys.socket: AF_INET, AF_INET6;
65 
66 import ocean.core.Test;
67 import ocean.core.TypeConvert;
68 import ocean.sys.ErrnoException;
69 import ocean.text.util.StringC;
70 
71 /*******************************************************************************
72 
73     Exception type to be thrown when fetching the IP address(es) for the
74     interface fails.
75 
76 *******************************************************************************/
77 
78 class ResolveIPException : ErrnoException
79 {
80 }
81 
82 /*******************************************************************************
83 
84     Returns IP addresses for the network interface.
85 
86     Params:
87         interface_name = Name of the interface (e.g. eth0)
88         ipv6 = true: fetch IPv6 addresses, false: IPv4
89 
90     Returns:
91         IP addresses of the interface for the given family as strings,
92         if they could be resolved, otherwise an empty array.
93 
94 *******************************************************************************/
95 
96 istring[] getAddrsForInterface( cstring interface_name, bool ipv6 = false )
97 {
98     istring[] addresses;
99     bool delegate_called = false;
100 
101     auto ret = getAddrsForInterface(interface_name, ipv6,
102         (cstring address, int getnameinfo_status)
103         {
104             delegate_called = true;
105 
106             if (getnameinfo_status != 0)
107             {
108                 throw (new ResolveIPException).set(getnameinfo_status,
109                                                    "getnameinfo failed");
110             }
111 
112             if (address.length)
113             {
114                 addresses ~= idup(address);
115             }
116 
117             return false;
118         });
119 
120     if (ret && !delegate_called)
121     {
122         throw (new ResolveIPException).useGlobalErrno("getifaddrs");
123     }
124 
125     return addresses;
126 }
127 
128 
129 /*******************************************************************************
130 
131     Iterates over IP addresses for the network interface.
132 
133     Obtains the network address of the local system from getifaddrs() and calls
134     dg with a host and service name string for each of these addresses. If host
135     and service name string formatting failed for an address, dg is called with
136     a null address and the status code of the conversion function,
137     getnameinfo(). See the manpage of getnameinfo() for its status codes.
138 
139     dg should return false to continue or true to stop iteration.
140 
141     If dg isn't called and return value is true, getifaddrs() has failed;
142     in this case check errno and see the getnameinfo() manpage.
143 
144     Params:
145         interface_name = Name of the interface (e.g. eth0)
146         ipv6 = true: fetch IPv6 addresses, false: IPv4
147         dg = iteration delegate
148 
149     Returns:
150         true if either dg returned true to stop the iteration or getifaddrs()
151         failed or false if the iteration finished normally.
152 
153 *******************************************************************************/
154 
155 bool getAddrsForInterface( cstring interface_name, bool ipv6,
156                            scope bool delegate ( cstring address,
157                                            int    getnameinfo_status ) dg )
158 {
159     ifaddrs* ifaddr;
160 
161     // Try to fetch a linked list of interfaces and their adresses
162     if (getifaddrs(&ifaddr) == -1)
163     {
164         return true;
165     }
166 
167     // ifaddr is allocated, and it needs to be freed!
168     scope(exit) freeifaddrs(ifaddr);
169 
170     auto salen  = ipv6? sockaddr_in6.sizeof : sockaddr_in.sizeof,
171          family = ipv6? AF_INET6 : AF_INET;
172 
173     // Iterate through each interface and check if the interface
174     // is the one that we're looking for.
175 
176     for (auto ifa = ifaddr; ifa !is null; ifa = ifa.ifa_next)
177     {
178         /***********************************************************************
179 
180             From the `getifaddrs` man page:
181             The ifa_addr field points to a structure containing the
182             interface address. (The sa_family subfield should be consulted
183             to determine the format of the address structure.) This field
184             may contain a null pointer.
185 
186         ***********************************************************************/
187 
188         if(!ifa.ifa_addr)
189         {
190             continue;
191         }
192 
193         if (interface_name != StringC.toDString(ifa.ifa_name))
194         {
195             continue;
196         }
197 
198         if (ifa.ifa_addr.sa_family != family)
199         {
200             continue;
201         }
202 
203         char[NI_MAXHOST] buffer;
204 
205         // Use getnameinfo to get the interface address
206 
207         auto result = getnameinfo(ifa.ifa_addr,
208                                    castFrom!(size_t).to!(uint)(salen),
209                                    buffer.ptr,
210                                    buffer.length,
211                                    null,
212                                    0,
213                                    NI_NUMERICHOST);
214 
215         // Check the result code and invoke the iteration delegate
216         if (dg(result? null : StringC.toDString(buffer.ptr), result))
217         {
218             return true;
219         }
220     }
221 
222     return false;
223 }