1 /******************************************************************************
2 
3     Memory-friendly utility to obtain the local or remote IPv4 or IPv6 socket
4     address.
5 
6     Wraps an associative array serving as map of parameter key and value
7     strings.
8     The parameter keys are set on instantiation; that is, a key list is passed
9     to the constructor. The keys cannot be changed, added or removed later by
10     ParamSet. However, a subclass can add keys.
11     All methods that accept a key handle the key case insensitively (except the
12     constructor). When keys are output, the original keys are used.
13     Note that keys and values are meant to slice string buffers in a subclass or
14     external to this class.
15 
16     Copyright:
17         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
18         All rights reserved.
19 
20     License:
21         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
22         Alternatively, this file may be distributed under the terms of the Tango
23         3-Clause BSD License (see LICENSE_BSD.txt for details).
24 
25  ******************************************************************************/
26 
27 module ocean.net.util.GetSocketAddress;
28 
29 import ocean.meta.types.Qualifiers;
30 import ocean.io.model.IConduit: ISelectable;
31 import ocean.sys.ErrnoException;
32 
33 import core.stdc.errno;
34 import core.stdc..string: strlen, strerror_r;
35 import core.sys.posix.arpa.inet: ntohs, inet_ntop, INET_ADDRSTRLEN, INET6_ADDRSTRLEN;
36 import core.sys.posix.netinet.in_: sa_family_t, in_port_t, sockaddr_in, sockaddr_in6, in_addr, in6_addr;
37 import core.sys.posix.sys.socket: AF_INET, AF_INET6, getsockname, getpeername,
38     socklen_t, sockaddr;
39 
40 
41 /******************************************************************************/
42 
43 class GetSocketAddress
44 {
45     /**************************************************************************
46 
47         Contains the address and accessor methods.
48 
49      **************************************************************************/
50 
51     struct Address
52     {
53         /**********************************************************************
54 
55             Address families: IPv4 and IPv6.
56 
57          **********************************************************************/
58 
59         enum Family : sa_family_t
60         {
61             INET  = .AF_INET,
62             INET6 = .AF_INET6
63         }
64 
65         /**********************************************************************
66 
67             Address data buffer
68 
69          **********************************************************************/
70 
71         static assert (INET6_ADDRSTRLEN >= INET_ADDRSTRLEN);
72 
73         private char[INET6_ADDRSTRLEN] addr_string_buffer;
74 
75         /**********************************************************************
76 
77             sockaddr struct instance, populated by getsockname()/getpeername().
78 
79          **********************************************************************/
80 
81         private sockaddr addr_;
82 
83         /**********************************************************************
84 
85             Reused SocketAddressException instance
86 
87          **********************************************************************/
88 
89         private SocketAddressException e;
90 
91         /**********************************************************************
92 
93             Returns:
94                 sockaddr struct instance as populated by getsockname()/
95                 getpeername().
96 
97          **********************************************************************/
98 
99         public sockaddr addr ( )
100         {
101             return this.addr_;
102         }
103 
104         /**********************************************************************
105 
106             Returns:
107                 address family
108 
109          **********************************************************************/
110 
111         public Family family ( )
112         {
113             return cast (Family) this.addr_.sa_family;
114         }
115 
116         /**********************************************************************
117 
118             Returns:
119                 true if the address family is supported by this struct
120                 (IPv4/IPv6 address) or false otherwise.
121 
122          **********************************************************************/
123 
124         public bool supported_family ( )
125         {
126             switch (this.family)
127             {
128                 case Family.INET, Family.INET6:
129                     return true;
130 
131                 default:
132                     return false;
133             }
134         }
135 
136         /**********************************************************************
137 
138             Returns:
139                 the address string.
140 
141             Throws:
142                 SocketAddressException if the socket address family is
143                 supported (other than IPv4 or IPv6).
144 
145          **********************************************************************/
146 
147         public cstring addr_string ( )
148         out (a)
149         {
150             assert (a);
151         }
152         do
153         {
154             void* addrp = &this.addr_;
155 
156             switch (this.family)
157             {
158                 case Family.INET:
159                     addrp += sockaddr_in.init.sin_addr.offsetof;
160                     break;
161 
162                 case Family.INET6:
163                     addrp += sockaddr_in6.init.sin6_addr.offsetof;
164                     break;
165 
166                 default:
167                     throw this.e.set(.EAFNOSUPPORT);
168             }
169 
170             auto str = .inet_ntop(this.addr_.sa_family, addrp,
171                                    this.addr_string_buffer.ptr, this.addr_string_buffer.length);
172 
173             this.e.enforce(!!str, "inet_ntop");
174 
175             return str[0 .. strlen(str)];
176         }
177 
178         /**********************************************************************
179 
180             Returns:
181                 the address port.
182 
183             Throws:
184                 SocketAddressException if the socket address family is
185                 supported (other than IPv4 or IPv6).
186 
187          **********************************************************************/
188 
189         public ushort port( )
190         {
191             in_port_t port;
192 
193             switch (this.family)
194             {
195                 case Family.INET:
196                     port = (cast (sockaddr_in*) &this.addr_).sin_port;
197                     break;
198 
199                 case Family.INET6:
200                     port = (cast (sockaddr_in6*) &this.addr_).sin6_port;
201                     break;
202 
203                 default:
204                     throw this.e.set(.EAFNOSUPPORT);
205             }
206 
207             return .ntohs(port);
208         }
209     }
210 
211     /**************************************************************************
212 
213         Reused SocketAddressException instance.
214 
215      **************************************************************************/
216 
217     private SocketAddressException e;
218 
219     /**************************************************************************
220 
221         Constructor.
222 
223      **************************************************************************/
224 
225     public this ( )
226     {
227         this.e = new SocketAddressException;
228     }
229 
230     /**************************************************************************
231 
232         Obtains the remote address associated with conduit from getpeername().
233         conduit must have been downcasted from Socket.
234 
235         Params:
236             conduit = socked conduit
237 
238         Returns:
239             the remote address associated with conduit.
240 
241         Throws:
242             SocketAddressException if getpeername() reports an error.
243 
244      **************************************************************************/
245 
246     public Address remote ( ISelectable conduit )
247     {
248         return this.get(conduit, &.getpeername, "getpeername");
249     }
250 
251     /**************************************************************************
252 
253         Obtains the local address associated with conduit from getsockname().
254         conduit must have been downcasted from Socket.
255 
256         Params:
257             conduit = socked conduit
258 
259         Returns:
260             the local address associated with conduit.
261 
262         Throws:
263             SocketAddressException if getpeername() reports an error.
264 
265      **************************************************************************/
266 
267     public Address local ( ISelectable conduit )
268     {
269         return this.get(conduit, &.getsockname, "getsockname");
270     }
271 
272     /**************************************************************************
273 
274         Obtains the local address associated with conduit from func().
275         conduit must have been downcast from Socket.
276 
277         Params:
278             conduit = socked conduit
279 
280         Returns:
281             an Address instance containing the output value of func().
282 
283         Throws:
284             SocketAddressException if getpeername() reports an error.
285 
286         In:
287             conduit must have been downcasted from Socket.
288 
289      **************************************************************************/
290 
291     private Address get ( ISelectable conduit, typeof (&.getsockname) func, istring funcname )
292     {
293         Address address;
294 
295         socklen_t len = address.addr_.sizeof;
296 
297         this.e.enforce(!func(conduit.fileHandle, cast (sockaddr*) &address.addr_, &len),
298                        "Cannot get local address from conduit", funcname);
299 
300         address.e = this.e;
301 
302         return address;
303     }
304 
305     /**************************************************************************/
306 
307     static class SocketAddressException : ErrnoException
308     {
309     }
310 }
311 
312 /*******************************************************************************
313 
314     Verify the bug of a null exception in GetSocketAddress is fixed.
315 
316 *******************************************************************************/
317 
318 version (unittest)
319 {
320     import ocean.core.Test;
321     import core.stdc.errno: EBADF;
322 }
323 
324 unittest
325 {
326     GetSocketAddress.SocketAddressException e = null;
327 
328     try
329     {
330         /*
331          * Call getsockname() with a mock conduit that returns a -1 file
332          * descriptor. It is guaranteed to fail with EBADF in this case, which
333          * the thrown exception should reflect.
334          */
335         (new GetSocketAddress).local(new class ISelectable
336         {
337             static assert(Handle.init == -1);
338             override Handle fileHandle ( ) {return Handle.init;}
339         });
340     }
341     catch (GetSocketAddress.SocketAddressException e_caught)
342     {
343         e = e_caught;
344     }
345 
346     test(e !is null);
347     test!("==")(e.errorNumber, EBADF);
348 }