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 }