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 }