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 }