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.meta.types.Qualifiers;
20 
21 import core.stdc.errno;
22 import core.stdc.string;
23 import core.sys.posix.arpa.inet;
24 import core.sys.posix.netdb : getnameinfo, NI_MAXHOST, NI_NUMERICHOST;
25 import core.sys.posix.netinet.in_: sockaddr_in, sockaddr_in6;
26 import core.sys.posix.sys.socket;
27 import core.sys.linux.ifaddrs;
28 import core.sys.posix.sys.socket: AF_INET, AF_INET6;
29 
30 import ocean.core.Test;
31 import ocean.core.TypeConvert;
32 import ocean.sys.ErrnoException;
33 import ocean.text.util.StringC;
34 
35 /*******************************************************************************
36 
37     Exception type to be thrown when fetching the IP address(es) for the
38     interface fails.
39 
40 *******************************************************************************/
41 
42 class ResolveIPException : ErrnoException
43 {
44 }
45 
46 /*******************************************************************************
47 
48     Returns IP addresses for the network interface.
49 
50     Params:
51         interface_name = Name of the interface (e.g. eth0)
52         ipv6 = true: fetch IPv6 addresses, false: IPv4
53 
54     Returns:
55         IP addresses of the interface for the given family as strings,
56         if they could be resolved, otherwise an empty array.
57 
58 *******************************************************************************/
59 
60 istring[] getAddrsForInterface( cstring interface_name, bool ipv6 = false )
61 {
62     istring[] addresses;
63     bool delegate_called = false;
64 
65     auto ret = getAddrsForInterface(interface_name, ipv6,
66         (cstring address, int getnameinfo_status)
67         {
68             delegate_called = true;
69 
70             if (getnameinfo_status != 0)
71             {
72                 throw (new ResolveIPException).set(getnameinfo_status,
73                                                    "getnameinfo failed");
74             }
75 
76             if (address.length)
77             {
78                 addresses ~= idup(address);
79             }
80 
81             return false;
82         });
83 
84     if (ret && !delegate_called)
85     {
86         throw (new ResolveIPException).useGlobalErrno("getifaddrs");
87     }
88 
89     return addresses;
90 }
91 
92 
93 /*******************************************************************************
94 
95     Iterates over IP addresses for the network interface.
96 
97     Obtains the network address of the local system from getifaddrs() and calls
98     dg with a host and service name string for each of these addresses. If host
99     and service name string formatting failed for an address, dg is called with
100     a null address and the status code of the conversion function,
101     getnameinfo(). See the manpage of getnameinfo() for its status codes.
102 
103     dg should return false to continue or true to stop iteration.
104 
105     If dg isn't called and return value is true, getifaddrs() has failed;
106     in this case check errno and see the getnameinfo() manpage.
107 
108     Params:
109         interface_name = Name of the interface (e.g. eth0)
110         ipv6 = true: fetch IPv6 addresses, false: IPv4
111         dg = iteration delegate
112 
113     Returns:
114         true if either dg returned true to stop the iteration or getifaddrs()
115         failed or false if the iteration finished normally.
116 
117 *******************************************************************************/
118 
119 bool getAddrsForInterface( cstring interface_name, bool ipv6,
120                            scope bool delegate ( cstring address,
121                                            int    getnameinfo_status ) dg )
122 {
123     ifaddrs* ifaddr;
124 
125     // Try to fetch a linked list of interfaces and their adresses
126     if (getifaddrs(&ifaddr) == -1)
127     {
128         return true;
129     }
130 
131     // ifaddr is allocated, and it needs to be freed!
132     scope(exit) freeifaddrs(ifaddr);
133 
134     auto salen  = ipv6? sockaddr_in6.sizeof : sockaddr_in.sizeof,
135          family = ipv6? AF_INET6 : AF_INET;
136 
137     // Iterate through each interface and check if the interface
138     // is the one that we're looking for.
139 
140     for (auto ifa = ifaddr; ifa !is null; ifa = ifa.ifa_next)
141     {
142         /***********************************************************************
143 
144             From the `getifaddrs` man page:
145             The ifa_addr field points to a structure containing the
146             interface address. (The sa_family subfield should be consulted
147             to determine the format of the address structure.) This field
148             may contain a null pointer.
149 
150         ***********************************************************************/
151 
152         if(!ifa.ifa_addr)
153         {
154             continue;
155         }
156 
157         if (interface_name != StringC.toDString(ifa.ifa_name))
158         {
159             continue;
160         }
161 
162         if (ifa.ifa_addr.sa_family != family)
163         {
164             continue;
165         }
166 
167         char[NI_MAXHOST] buffer;
168 
169         // Use getnameinfo to get the interface address
170 
171         auto result = getnameinfo(ifa.ifa_addr,
172                                    castFrom!(size_t).to!(uint)(salen),
173                                    buffer.ptr,
174                                    buffer.length,
175                                    null,
176                                    0,
177                                    NI_NUMERICHOST);
178 
179         // Check the result code and invoke the iteration delegate
180         if (dg(result? null : StringC.toDString(buffer.ptr), result))
181         {
182             return true;
183         }
184     }
185 
186     return false;
187 }