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 }