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 
30 import ocean.transition;
31 
32 import ocean.io.model.IConduit: ISelectable;
33 
34 import ocean.stdc.posix.sys.socket: getsockname, getpeername, socklen_t, sockaddr;
35 
36 import ocean.stdc.posix.arpa.inet: ntohs, inet_ntop, INET_ADDRSTRLEN, INET6_ADDRSTRLEN;
37 
38 import ocean.stdc.posix.netinet.in_: sa_family_t, in_port_t, sockaddr_in, sockaddr_in6, in_addr, in6_addr;
39 
40 import core.stdc.errno;
41 
42 import consts = core.sys.posix.sys.socket;
43 
44 import ocean.stdc.string: strlen;
45 
46 import ocean.sys.ErrnoException;
47 
48 extern (C) private char* strerror_r(int n, char* dst, size_t dst_length);
49 
50 /******************************************************************************/
51 
52 class GetSocketAddress
53 {
54     /**************************************************************************
55 
56         Contains the address and accessor methods.
57 
58      **************************************************************************/
59 
60     struct Address
61     {
62         /**********************************************************************
63 
64             Address families: IPv4 and IPv6.
65 
66          **********************************************************************/
67 
68         enum Family : sa_family_t
69         {
70             INET  = consts.AF_INET,
71             INET6 = consts.AF_INET6
72         }
73 
74         /**********************************************************************
75 
76             Address data buffer
77 
78          **********************************************************************/
79 
80         static assert (INET6_ADDRSTRLEN >= INET_ADDRSTRLEN);
81 
82         private char[INET6_ADDRSTRLEN] addr_string_buffer;
83 
84         /**********************************************************************
85 
86             sockaddr struct instance, populated by getsockname()/getpeername().
87 
88          **********************************************************************/
89 
90         private sockaddr addr_;
91 
92         /**********************************************************************
93 
94             Reused SocketAddressException instance
95 
96          **********************************************************************/
97 
98         private SocketAddressException e;
99 
100         /**********************************************************************
101 
102             Returns:
103                 sockaddr struct instance as populated by getsockname()/
104                 getpeername().
105 
106          **********************************************************************/
107 
108         public sockaddr addr ( )
109         {
110             return (&this).addr_;
111         }
112 
113         /**********************************************************************
114 
115             Returns:
116                 address family
117 
118          **********************************************************************/
119 
120         public Family family ( )
121         {
122             return cast (Family) (&this).addr_.sa_family;
123         }
124 
125         /**********************************************************************
126 
127             Returns:
128                 true if the address family is supported by this struct
129                 (IPv4/IPv6 address) or false otherwise.
130 
131          **********************************************************************/
132 
133         public bool supported_family ( )
134         {
135             switch ((&this).family)
136             {
137                 case Family.INET, Family.INET6:
138                     return true;
139 
140                 default:
141                     return false;
142             }
143         }
144 
145         /**********************************************************************
146 
147             Returns:
148                 the address string.
149 
150             Throws:
151                 SocketAddressException if the socket address family is
152                 supported (other than IPv4 or IPv6).
153 
154          **********************************************************************/
155 
156         public cstring addr_string ( )
157         out (a)
158         {
159             assert (a);
160         }
161         body
162         {
163             void* addrp = &(&this).addr_;
164 
165             switch ((&this).family)
166             {
167                 case Family.INET:
168                     addrp += sockaddr_in.init.sin_addr.offsetof;
169                     break;
170 
171                 case Family.INET6:
172                     addrp += sockaddr_in6.init.sin6_addr.offsetof;
173                     break;
174 
175                 default:
176                     throw (&this).e.set(.EAFNOSUPPORT);
177             }
178 
179             auto str = .inet_ntop((&this).addr_.sa_family, addrp,
180                                    (&this).addr_string_buffer.ptr, (&this).addr_string_buffer.length);
181 
182             (&this).e.enforce(!!str, "inet_ntop");
183 
184             return str[0 .. strlen(str)];
185         }
186 
187         /**********************************************************************
188 
189             Returns:
190                 the address port.
191 
192             Throws:
193                 SocketAddressException if the socket address family is
194                 supported (other than IPv4 or IPv6).
195 
196          **********************************************************************/
197 
198         public ushort port( )
199         {
200             in_port_t port;
201 
202             switch ((&this).family)
203             {
204                 case Family.INET:
205                     port = (cast (sockaddr_in*) &(&this).addr_).sin_port;
206                     break;
207 
208                 case Family.INET6:
209                     port = (cast (sockaddr_in6*) &(&this).addr_).sin6_port;
210                     break;
211 
212                 default:
213                     throw (&this).e.set(.EAFNOSUPPORT);
214             }
215 
216             return .ntohs(port);
217         }
218     }
219 
220     /**************************************************************************
221 
222         Reused SocketAddressException instance.
223 
224      **************************************************************************/
225 
226     private SocketAddressException e;
227 
228     /**************************************************************************
229 
230         Constructor.
231 
232      **************************************************************************/
233 
234     public this ( )
235     {
236         this.e = new SocketAddressException;
237     }
238 
239     /**************************************************************************
240 
241         Obtains the remote address associated with conduit from getpeername().
242         conduit must have been downcasted from Socket.
243 
244         Params:
245             conduit = socked conduit
246 
247         Returns:
248             the remote address associated with conduit.
249 
250         Throws:
251             SocketAddressException if getpeername() reports an error.
252 
253      **************************************************************************/
254 
255     public Address remote ( ISelectable conduit )
256     {
257         return this.get(conduit, &.getpeername, "getpeername");
258     }
259 
260     /**************************************************************************
261 
262         Obtains the local address associated with conduit from getsockname().
263         conduit must have been downcasted from Socket.
264 
265         Params:
266             conduit = socked conduit
267 
268         Returns:
269             the local address associated with conduit.
270 
271         Throws:
272             SocketAddressException if getpeername() reports an error.
273 
274      **************************************************************************/
275 
276     public Address local ( ISelectable conduit )
277     {
278         return this.get(conduit, &.getsockname, "getsockname");
279     }
280 
281     /**************************************************************************
282 
283         Obtains the local address associated with conduit from func().
284         conduit must have been downcast from Socket.
285 
286         Params:
287             conduit = socked conduit
288 
289         Returns:
290             an Address instance containing the output value of func().
291 
292         Throws:
293             SocketAddressException if getpeername() reports an error.
294 
295         In:
296             conduit must have been downcasted from Socket.
297 
298      **************************************************************************/
299 
300     private Address get ( ISelectable conduit, typeof (&.getsockname) func, istring funcname )
301     {
302         Address address;
303 
304         socklen_t len = address.addr_.sizeof;
305 
306         this.e.enforce(!func(conduit.fileHandle, cast (sockaddr*) &address.addr_, &len),
307                        "Cannot get local address from conduit", funcname);
308 
309         address.e = this.e;
310 
311         return address;
312     }
313 
314     /**************************************************************************/
315 
316     static class SocketAddressException : ErrnoException
317     {
318     }
319 }
320 
321 /*******************************************************************************
322 
323     Verify the bug of a null exception in GetSocketAddress is fixed.
324 
325 *******************************************************************************/
326 
327 version (UnitTest)
328 {
329     import ocean.core.Test;
330     import core.stdc.errno: EBADF;
331 }
332 
333 unittest
334 {
335     GetSocketAddress.SocketAddressException e = null;
336 
337     try
338     {
339         /*
340          * Call getsockname() with a mock conduit that returns a -1 file
341          * descriptor. It is guaranteed to fail with EBADF in this case, which
342          * the thrown exception should reflect.
343          */
344         (new GetSocketAddress).local(new class ISelectable
345         {
346             static assert(Handle.init == -1);
347             override Handle fileHandle ( ) {return Handle.init;}
348         });
349     }
350     catch (GetSocketAddress.SocketAddressException e_caught)
351     {
352         e = e_caught;
353     }
354 
355     test(e !is null);
356     test!("==")(e.errorNumber, EBADF);
357 }