1 /*******************************************************************************
2 
3     IP socket that memorises its address
4 
5     TODO: Add an AddressIPSocket subclass with host resolution.
6 
7     Copyright:
8         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
9         All rights reserved.
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13         Alternatively, this file may be distributed under the terms of the Tango
14         3-Clause BSD License (see LICENSE_BSD.txt for details).
15 
16 *******************************************************************************/
17 
18 module ocean.sys.socket.AddressIPSocket;
19 
20 import ocean.sys.socket.model.IAddressIPSocketInfo;
21 
22 import ocean.sys.socket.IPSocket,
23        ocean.sys.socket.InetAddress,
24        ocean.sys.socket.AddrInfo;
25 
26 import ocean.meta.types.Qualifiers;
27 
28 import ocean.io.device.Conduit: ISelectable;
29 
30 import core.stdc.string: strlen;
31 
32 
33 /******************************************************************************
34 
35     IP socket class, memorises the address most recently passed to bind() or
36     connect() or obtained by accept().
37 
38     Params:
39         IPv6 = true: use IPv6, false: use IPv4
40 
41  ******************************************************************************/
42 
43 class AddressIPSocket ( bool IPv6 = false ) : IPSocket!(IPv6), IAddressIPSocketInfo
44 {
45     /**************************************************************************
46 
47         Internet address
48 
49      **************************************************************************/
50 
51     private InetAddress in_address;
52 
53     /**************************************************************************
54 
55         Internet address string buffer, passed to inet_pton()/inet_ntop().
56 
57      **************************************************************************/
58 
59     private char[in_address.addrstrlen] ip_address_;
60 
61     /**************************************************************************
62 
63         Number of valid characters in address_.
64 
65      **************************************************************************/
66 
67     private size_t ip_address_len = 0;
68 
69     /**************************************************************************
70 
71         Obtains the IP address most recently passed to bind() or connect() or
72         obtained by accept().
73 
74         Returns:
75             the current IP address.
76 
77      **************************************************************************/
78 
79     public cstring address ( )
80     {
81         return this.ip_address_[0 .. this.ip_address_len];
82     }
83 
84     /***************************************************************************
85 
86         Asks the operating system what address & port this socket is actually
87         bound to and updates the internal buffers accordingly.
88 
89         This can be useful when you bind to port 0 which means that the OS will
90         choose a random port for you and you want to find out which one was
91         chosen.
92 
93         Returns:
94             0 on success, -1 on failure
95 
96     ***************************************************************************/
97 
98     public int updateAddress ( )
99     {
100         return this.getsockname(this.in_address.addr);
101     }
102 
103     /**************************************************************************
104 
105         Obtains the port number most recently passed to bind() or connect() or
106         obtained by accept().
107 
108         Returns:
109             the current port number.
110 
111      **************************************************************************/
112 
113     public ushort port ( )
114     {
115         return this.in_address.port;
116     }
117 
118     /***************************************************************************
119 
120         Returns:
121             true if a client connection is currently established or false if
122             not.
123 
124     ***************************************************************************/
125 
126     public bool connected ( )
127     {
128         return this.fileHandle >= 0;
129     }
130 
131     /***************************************************************************
132 
133         IAddressIPSocketInfo interface method. Wrapper for method implemented by
134         super class.
135 
136         Returns:
137             I/O device instance (file descriptor under linux)
138 
139     ***************************************************************************/
140 
141     public override Handle fileHandle ( )
142     {
143         return super.fileHandle();
144     }
145 
146     /**************************************************************************
147 
148         Obtains the address most recently passed to bind() or connect() or
149         obtained by accept().
150 
151         Returns:
152             the current address.
153 
154      **************************************************************************/
155 
156     public InAddr in_addr ( )
157     {
158         return this.in_address.addr;
159     }
160 
161     /**************************************************************************
162 
163         Assigns a local address and optionally a port to this socket and
164         memorises address and port. This socket needs to have been created by
165         socket().
166 
167         Params:
168             local_ip_address = local IP address
169             local_port       = local port or 0 to use the wildcard "any" port
170 
171         Returns:
172             0 on success or -1 on failure. On failure errno is set
173             appropriately.
174 
175         Errors:
176             as the overridden method but also sets errno to EINVAL if the
177             address does not contain a valid IP address string.
178 
179      **************************************************************************/
180 
181     public override int bind ( cstring local_ip_address, ushort local_port = 0 )
182     {
183         if (auto a = this.in_address(local_ip_address, local_port))
184         {
185             scope (exit) this.setAddress();
186             return super.bind(a);
187         }
188         else
189         {
190             return -1;
191         }
192     }
193 
194     /**************************************************************************
195 
196         Assigns the wildcard "any" local address and optionally a port to this
197         socket and memorises address and port. This socket needs to have been
198         created by socket().
199 
200         Params:
201             local_port = local port or 0 to use the wildcard "any" port
202 
203         Returns:
204             0 on success or -1 on failure. On failure errno is set
205             appropriately.
206 
207      **************************************************************************/
208 
209     public override int bind ( ushort local_port )
210     {
211         scope (exit) this.setAddress();
212 
213         return super.bind(this.in_address(local_port));
214     }
215 
216     /**************************************************************************
217 
218         Connects this socket the specified address and port and memorises
219         address and port. This socket needs to have been created by socket().
220 
221         Params:
222             remote_ip_address = remote IP address
223             remote_port       = remote port
224 
225         Returns:
226             0 on success or -1 on failure. On failure errno is set
227             appropriately.
228 
229         Errors:
230             as the overridden method but also sets errno to EINVAL if the
231             address does not contain a valid IP address string.
232 
233      **************************************************************************/
234 
235     public override int connect ( cstring remote_ip_address, ushort remote_port )
236     {
237         if (auto a = this.in_address(remote_ip_address, remote_port))
238         {
239             scope (exit) this.setAddress();
240             return super.connect(a);
241         }
242         else
243         {
244             return -1;
245         }
246     }
247 
248     /**************************************************************************
249 
250         Connects this socket the specified address memorises and it. This socket
251         needs to have been created by socket().
252 
253         Params:
254             remote_address = remote address
255 
256         Returns:
257             0 on success or -1 on failure. On failure errno is set
258             appropriately.
259 
260         Errors:
261             as above.
262 
263      **************************************************************************/
264 
265     public override int connect ( InAddr remote_address )
266     {
267         scope (exit) this.setAddress();
268 
269         return super.connect(this.in_address = remote_address);
270     }
271 
272     /**************************************************************************
273 
274         Calls accept() to accept a connection from a listening socket, sets
275         this.fd to the accepted socket file descriptor and memorises the remote
276         address and port.
277 
278         Params:
279             listening_socket = the listening socket to accept the new connection
280                                from
281             remote_address   = filled in with the address of the peer socket, as
282                                known to the communications layer
283             flags            = socket flags, see description above
284 
285         Returns:
286             the file descriptor of the accepted socket on success or -1 on
287             failure. On failure errno is set appropriately.
288 
289      **************************************************************************/
290 
291     public override int accept ( ISelectable listening_socket,
292                                  ref InAddr remote_address,
293                                  SocketFlags flags = SocketFlags.None )
294     {
295         scope (exit) this.setAddress(remote_address);
296 
297         return super.accept(listening_socket, remote_address, flags);
298     }
299 
300     /**************************************************************************
301 
302         Calls accept() to accept a connection from a listening socket, sets
303         this.fd to the accepted socket file descriptor and memorises the remote
304         address and port.
305 
306         Params:
307             listening_socket = the listening socket to accept the new connection
308                                from
309             remote_address   = filled in with the address of the peer socket, as
310                                known to the communications layer
311             nonblocking      = true: make the accepted socket nonblocking
312                                false: leave it blocking
313 
314         Returns:
315             the file descriptor of the accepted socket on success or -1 on
316             failure. On failure errno is set appropriately.
317 
318      **************************************************************************/
319 
320     public override int accept ( ISelectable listening_socket,
321                                  ref InAddr remote_address, bool nonblocking )
322     {
323         scope (exit) this.setAddress(remote_address);
324 
325         return super.accept(listening_socket, remote_address, nonblocking);
326     }
327 
328 
329     /**************************************************************************
330 
331         Calls accept() to accept a connection from a listening socket, sets
332         this.fd to the accepted socket file descriptor and memorises the remote
333         address and port.
334 
335         Params:
336             listening_socket = the listening socket to accept the new connection
337                                from
338             flags            = socket flags, see description above
339 
340         Returns:
341             the file descriptor of the accepted socket on success or -1 on
342             failure. On failure errno is set appropriately.
343 
344      **************************************************************************/
345 
346     public override int accept ( ISelectable listening_socket,
347                                  SocketFlags flags = SocketFlags.init )
348     {
349         scope (exit) this.setAddress();
350 
351         return super.accept(listening_socket, this.in_address.addr, flags);
352     }
353 
354     /**************************************************************************
355 
356         Calls accept() to accept a connection from a listening socket, sets
357         this.fd to the accepted socket file descriptor and memorises the remote
358         address and port.
359 
360         Params:
361             listening_socket = the listening socket to accept the new connection
362                                from
363             nonblocking      = true: make the accepted socket nonblocking,
364                                false: leave it blocking
365 
366         Returns:
367             the file descriptor of the accepted socket on success or -1 on
368             failure. On failure errno is set appropriately.
369 
370      **************************************************************************/
371 
372 
373     public override int accept ( ISelectable listening_socket,
374                                  bool nonblocking )
375     {
376         scope (exit) this.setAddress();
377 
378         return super.accept(listening_socket,
379                             this.in_address.addr, nonblocking);
380     }
381 
382     /**************************************************************************
383 
384         Sets this.in_address to address and updates this.ip_address_.
385 
386         Params:
387             address = input address
388 
389      **************************************************************************/
390 
391     private void setAddress ( InAddr address )
392     {
393         this.in_address = address;
394         this.setAddress();
395     }
396 
397     /**************************************************************************
398 
399         Updates this.ip_address_.
400 
401      **************************************************************************/
402 
403     private void setAddress ( )
404     {
405         this.ip_address_len = this.in_address.inet_ntop(this.ip_address_).length;
406     }
407 }
408 
409 // Test that methods that accept IP addresses in presentation notation
410 // correctly accept valid and reject invalid addresses.
411 
412 version (unittest)
413 {
414     import ocean.core.Test;
415     import core.stdc.errno: errno, EINVAL, EBADF;
416 }
417 
418 unittest
419 {
420     {
421         scope s = new AddressIPSocket!();
422         // With s.fd == -1 the connect(2) call should fail with EBADF if the
423         // inet_pton(3) call succeeds.
424         test(s.fd == -1);
425 
426         errno = 0;
427         test!("==")(s.bind("127.0.0.1", 12345), -1);
428         test!("==")(errno, EBADF);
429         test!("==")(s.address, "127.0.0.1");
430         test!("==")(s.port, 12345);
431 
432         errno = 0;
433         test!("==")(s.bind("Hello World!", 6789), -1);
434         test!("==")(errno, EINVAL);
435         test!("==")(s.address, "127.0.0.1");
436         test!("==")(s.port, 12345);
437 
438         errno = 0;
439         test!("==")(s.connect("127.0.0.2", 9876), -1);
440         test!("==")(errno, EBADF);
441         test!("==")(s.address, "127.0.0.2");
442         test!("==")(s.port, 9876);
443 
444         errno = 0;
445         test!("==")(s.connect("Hello World!", 54321), -1);
446         test!("==")(errno, EINVAL);
447         test!("==")(s.address, "127.0.0.2");
448         test!("==")(s.port, 9876);
449     }
450 
451     {
452         scope s = new AddressIPSocket!(true);
453         test(s.fd == -1);
454 
455         errno = 0;
456         // IP address taken from the Linux manual page for inet_pton(3).
457         test!("==")(s.bind("::ffff:204.152.189.116", 12345), -1);
458         test!("==")(errno, EBADF);
459         test!("==")(s.address, "::ffff:204.152.189.116");
460         test!("==")(s.port, 12345);
461 
462         errno = 0;
463         test!("==")(s.bind("Hello World!", 6789), -1);
464         test!("==")(errno, EINVAL);
465         test!("==")(s.address, "::ffff:204.152.189.116");
466         test!("==")(s.port, 12345);
467 
468         errno = 0;
469         test!("==")(s.connect("::ffff:204.152.189.117", 9876), -1);
470         test!("==")(errno, EBADF);
471         test!("==")(s.address, "::ffff:204.152.189.117");
472         test!("==")(s.port, 9876);
473 
474         errno = 0;
475         test!("==")(s.connect("Hello World!", 54321), -1);
476         test!("==")(errno, EINVAL);
477         test!("==")(s.address, "::ffff:204.152.189.117");
478         test!("==")(s.port, 9876);
479     }
480 }