1 /// Various networking support tools
2 module mecca.lib.net;
3 
4 // Licensed under the Boost license. Full copyright information in the AUTHORS file
5 
6 import core.stdc.errno : errno, EINVAL;
7 import core.sys.posix.sys.socket;
8 public import core.sys.posix.sys.socket: AF_INET, AF_UNIX, AF_INET6, AF_UNSPEC;
9 import core.sys.posix.netinet.in_;
10 import core.sys.posix.sys.un;
11 import core.sys.posix.netdb;
12 import std.conv;
13 
14 import mecca.containers.arrays: FixedString, setStringzLength;
15 import mecca.lib.exception;
16 import mecca.lib..string;
17 import mecca.log;
18 
19 /// An IPv4 address
20 @FMT("{bytes[0]}.{bytes[1]}.{bytes[2]}.{bytes[3]}")
21 struct IPv4 {
22     enum IPv4 loopback  = IPv4(cast(ubyte[])[127, 0, 0, 1]);            /// Lookpack address
23     enum IPv4 none      = IPv4(cast(ubyte[])[255, 255, 255, 255]);      /// No address
24     enum IPv4 broadcast = IPv4(cast(ubyte[])[255, 255, 255, 255]);      /// Broadcast address
25     enum IPv4 any       = IPv4(cast(ubyte[])[0, 0, 0, 0]);              /// "Any" address (for local binding)
26 
27     union {
28         in_addr inaddr = in_addr(0xffffffff);   /// Address as an `in_addr`
29         ubyte[4] bytes;                         /// Address as array of bytes
30     }
31 
32     /// Construct an IPv4 address
33     this(uint netOrder) pure nothrow @safe @nogc {
34         inaddr.s_addr = netOrder;
35     }
36     /// ditto
37     this(ubyte[4] bytes) pure nothrow @safe @nogc {
38         this.bytes = bytes;
39     }
40     /// ditto
41     this(in_addr ia) pure nothrow @safe @nogc {
42         inaddr = ia;
43     }
44     /// ditto
45     this(string dottedString) @safe @nogc {
46         opAssign(dottedString);
47     }
48 
49     /// Assignment
50     ref typeof(this) opAssign(uint netOrder) return pure nothrow @safe @nogc {
51         inaddr.s_addr = netOrder;
52         return this;
53     }
54     /// ditto
55     ref typeof(this) opAssign(ubyte[4] bytes) return pure nothrow @safe @nogc {
56         this.bytes = bytes;
57         return this;
58     }
59     /// ditto
60     ref typeof(this) opAssign(in_addr ia) return pure nothrow @safe @nogc {
61         inaddr = ia;
62         return this;
63     }
64     /// ditto
65     ref typeof(this) opAssign(string dottedString) return @trusted @nogc {
66         int res = inet_pton(AF_INET, ToStringz!INET_ADDRSTRLEN(dottedString), &inaddr);
67 
68         DBG_ASSERT!"Invalid call to inet_pton"(res>=0);
69         if( res!=1 ) {
70             errno = EINVAL;
71             throw mkEx!ErrnoException("Invalid IPv4 address to SockAddrIPv4 constructor");
72         }
73 
74         return this;
75     }
76 
77     /// Return the address in network order
78     @property uint netOrder() const pure nothrow @safe @nogc {
79         return inaddr.s_addr;
80     }
81     /// Return the address in host order
82     @property uint hostOrder() const pure nothrow @safe @nogc {
83         return ntohl(inaddr.s_addr);
84     }
85     /// Is the address a valid one
86     ///
87     /// The broadcast address is not considered valid.
88     @property bool isValid() const pure nothrow @safe @nogc {
89         return inaddr.s_addr != 0 && inaddr.s_addr != 0xffffffff;
90     }
91 
92     /// Return a GC allocated string for the address (dot notation)
93     string toString() const nothrow @trusted {
94         char[INET_ADDRSTRLEN] buffer;
95         ASSERT!"Address translation failed"(inet_ntop(AF_INET, &inaddr, buffer.ptr, INET_ADDRSTRLEN) !is null);
96 
97         return to!string(buffer.ptr);
98     }
99 
100     //
101     // netmasks
102     //
103     /// Return a mask corresponding to a network of 2^^bits addresses
104     static IPv4 mask(ubyte bits) nothrow @safe @nogc {
105         return IPv4((1 << bits) - 1);
106     }
107 
108     /// Return how many bits in the current mask
109     ///
110     /// Object must be initialized to a valid mask address
111     ubyte maskBits() pure nothrow @safe @nogc {
112         if (netOrder == 0) {
113             return 0;
114         }
115         import core.bitop: bsf;
116         assert(isMask, "not a mask");
117         int leastSignificantSetBit = bsf(cast(uint)hostOrder);
118         auto maskBits = (leastSignificantSetBit.sizeof * 8) - leastSignificantSetBit;
119         return cast(byte)maskBits;
120     }
121 
122     /// Return whether current address is a valid mask address
123     public bool isMask() pure const nothrow @safe @nogc {
124         return (~hostOrder & (~hostOrder + 1)) == 0;
125     }
126 
127     /// Return the intersection of the two addresses
128     ///
129     /// This is useful to extract network name and client name from an address
130     IPv4 opBinary(string op: "&")(IPv4 rhs) const nothrow @safe @nogc {
131         return IPv4(inaddr.s_addr & rhs.inaddr.s_addr);
132     }
133 
134     /// Returns whether our IP and host are in the same network
135     bool isInSubnet(IPv4 host, IPv4 mask) const nothrow @safe @nogc {
136         assert(mask.isMask, "mask argument is not valid");
137         return (host & mask) == (this & mask);
138     }
139 
140     static assert (this.sizeof == in_addr.sizeof);
141 }
142 
143 
144 unittest {
145     import std.stdio;
146     assert(IPv4.loopback.toString() == "127.0.0.1");
147     auto ip = IPv4("1.2.3.4");
148     assert(ip.toString() == "1.2.3.4");
149     assert(ip.netOrder == 0x04030201);
150     assert(ip.hostOrder == 0x01020304);
151 
152     auto m = ip.mask(24);
153     assert(m.toString == "255.255.255.0");
154     assert((ip & m) == IPv4("1.2.3.0"));
155     ip = "172.16.0.195";
156     assert(ip.toString == "172.16.0.195");
157     ip = IPv4.loopback;
158     assert(ip.toString == "127.0.0.1");
159     assert(ip.bytes[0] == 127);
160 }
161 
162 /// A D representation of the `sockaddr_in` struct
163 struct SockAddrIPv4 {
164     /// Unspecified port constant
165     enum PORT_ANY = 0;
166 
167     /// the underlying `sockaddr_in` struct
168     sockaddr_in sa;
169 
170     /// Construct a SockAddrIPv4
171     this(in_addr addr, ushort port = PORT_ANY) nothrow @safe @nogc {
172         sa.sin_family = AF_INET;
173         sa.sin_addr = addr;
174         this.port = port;
175     }
176 
177     /// ditto
178     this(string addr, ushort port = PORT_ANY) @trusted @nogc {
179         int res = inet_pton(AF_INET, toStringzNGC(addr), &sa.sin_addr);
180 
181         DBG_ASSERT!"Invalid call to inet_pton(%s)"(res>=0, addr);
182         if( res!=1 ) {
183             errno = EINVAL;
184             throw mkEx!ErrnoException("Invalid IPv4 address to SockAddrIPv4 constructor");
185         }
186 
187         sa.sin_family = AF_INET;
188         this.port = port;
189     }
190 
191     /// ditto
192     this(const sockaddr* sa, socklen_t length) nothrow @trusted @nogc {
193         ASSERT!"Wrong address family for IPv4. %s instead of %s"(sa.sa_family == AF_INET, sa.sa_family, AF_INET);
194         ASSERT!"IPv4 sockaddr too short. %s<%s"(length >= sockaddr_in.sizeof, length, sockaddr_in.sizeof);
195         this.sa = *cast(sockaddr_in*)sa;
196     }
197 
198     /// ditto
199     this(IPv4 addr, ushort port = PORT_ANY) nothrow @safe @nogc {
200         this(addr.inaddr, port);
201     }
202 
203     /// Get/set the sa's port in $(B host) byte order
204     @property void port(ushort newPort) nothrow @safe @nogc {
205         sa.sin_port = htons(newPort);
206     }
207 
208     /// ditto
209     @property ushort port() const pure nothrow @safe @nogc {
210         return ntohs(sa.sin_port);
211     }
212 
213     /// Get/set the sa's addr
214     @property void addr(IPv4 address) nothrow @safe @nogc {
215         sa.sin_addr = address.inaddr;
216     }
217 
218     /// ditto
219     @property IPv4 addr() const pure nothrow @safe @nogc {
220         return IPv4(sa.sin_addr);
221     }
222 
223     /// Convert the address to GC allocated string in the format addr:port
224     string toString() const nothrow @safe {
225         return toStringAddr() ~ ":" ~ toStringPort();
226     }
227 
228     /// Convert just the address part to a GC allocated string
229     string toStringAddr() const nothrow @safe {
230         if( sa.sin_family != AF_INET )
231             return "<Invalid IPv4 address>";
232 
233         return IPv4(sa.sin_addr).toString();
234     }
235 
236     auto toFixedStringAddr() const nothrow @trusted @nogc {
237         ASSERT!"Address family is %s, not IPv4"( sa.sin_family == AF_INET, sa.sin_family );
238 
239         FixedString!INET_ADDRSTRLEN buf;
240         buf.length = buf.capacity;
241         ASSERT!"Address translation failed"(inet_ntop(AF_INET, &sa.sin_addr, buf.ptr, buf.len) !is null);
242         setStringzLength(buf);
243 
244         return buf;
245     }
246 
247     /// Convert just the port part to a GC allocated string
248     string toStringPort() const nothrow @safe {
249         if( port!=PORT_ANY )
250             return to!string(port);
251         else
252             return "*";
253     }
254 
255     /// Construct a loopback sockaddr for the given port
256     static SockAddrIPv4 loopback(ushort port = PORT_ANY) nothrow @safe @nogc {
257         return SockAddrIPv4(IPv4.loopback, port);
258     }
259 
260     /// Construct an any sockaddr for the given port
261     static SockAddrIPv4 any(ushort port = PORT_ANY) nothrow @safe @nogc {
262         return SockAddrIPv4(IPv4.any, port);
263     }
264 
265     /// Construct a broadcast sockaddr for the given port
266     static SockAddrIPv4 broadcast(ushort port = PORT_ANY) nothrow @safe @nogc {
267         return SockAddrIPv4(IPv4.broadcast, port);
268     }
269 }
270 
271 unittest {
272     SockAddrIPv4 s1 = SockAddrIPv4.loopback(1234);
273     SockAddrIPv4 s2 = SockAddrIPv4(in_addr(htonl(0x0a0b0c0d)));
274 
275     assertEQ(s1.toString(), "127.0.0.1:1234");
276     assertEQ(s1.toStringAddr(), "127.0.0.1");
277     assertEQ(s1.toFixedStringAddr(), "127.0.0.1");
278     assertEQ(s1.toStringPort(), "1234");
279     //assertEQ(s1.addrFixedString(), "127.0.0.1");
280     assertEQ(s2.toString(), "10.11.12.13:*");
281     //assertEQ(s2.addrFixedString(), "10.11.12.13");
282 }
283 
284 /// An IPv6 address
285 struct IPv6 {
286     enum any = IPv6(cast(ubyte[ADDR_LEN])[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);            /// "Any" address (for local binding)
287     enum loopback = IPv6(cast(ubyte[ADDR_LEN])[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]);       /// Loopback address
288 
289     enum ADDR_LEN = 16; /// Length of an IPv6 address
290 
291     union {
292         in6_addr inaddr;        /// Address as `in6_addr`
293         ubyte[ADDR_LEN] bytes;  /// Address as array of bytes
294     }
295 
296     /// Construct an IPv6 address
297     this(ref const in6_addr ia) nothrow @safe @nogc {
298         this.inaddr = ia;
299     }
300 
301     /// ditto
302     this(ubyte[ADDR_LEN] bytes) nothrow @safe @nogc {
303         this.bytes = bytes;
304     }
305 
306     /// ditto
307     this(string dottedString) @safe @nogc {
308         opAssign(dottedString);
309     }
310     /// Assignment
311     ref auto opAssign(ref const in6_addr ia) nothrow @safe @nogc {
312         this.inaddr = ia;
313         return this;
314     }
315     /// ditto
316     ref auto opAssign(ubyte[16] bytes) nothrow @safe @nogc {
317         this.bytes = bytes;
318         return this;
319     }
320     /// ditto
321     ref auto opAssign(string dottedString) @trusted @nogc {
322         int res = inet_pton(AF_INET6, ToStringz!INET6_ADDRSTRLEN(dottedString), &inaddr);
323 
324         DBG_ASSERT!"Invalid call to inet_pton"(res>=0);
325         if( res!=1 ) {
326             errno = EINVAL;
327             throw mkEx!ErrnoException("Invalid IPv6 address to SockAddrIPv6 constructor");
328         }
329 
330         return this;
331     }
332 
333     /// Return a GC allocated string for the address (colon notation)
334     string toString() const @trusted {
335         char[INET6_ADDRSTRLEN] buf;
336         errnoEnforceNGC(inet_ntop(AF_INET6, bytes.ptr, buf.ptr, buf.length) !is null, "inet_ntop failed");
337         return to!string(buf.ptr);
338     }
339 
340     /// Returns true if this is the unspecified address
341     @property bool isUnspecified() pure const @trusted @nogc {
342         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
343         return IN6_IS_ADDR_UNSPECIFIED(cast(in6_addr*)&inaddr) != 0;
344     }
345     /// Returns true if this is the loopback address
346     @property bool isLoopback() pure const @trusted @nogc {
347         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
348         return IN6_IS_ADDR_LOOPBACK(cast(in6_addr*)&inaddr) != 0;
349     }
350     /// Returns true if this is a multicast address
351     @property bool isMulticast() pure const @trusted @nogc {
352         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
353         return IN6_IS_ADDR_MULTICAST(cast(in6_addr*)&inaddr) != 0;
354     }
355     /// Returns true if this is a link local address
356     @property bool isLinkLocal() pure const @trusted @nogc {
357         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
358         return IN6_IS_ADDR_LINKLOCAL(cast(in6_addr*)&inaddr) != 0;
359     }
360     /// Returns true if this is a site local address
361     @property bool isSiteLocal() pure const @trusted @nogc {
362         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
363         return IN6_IS_ADDR_SITELOCAL(cast(in6_addr*)&inaddr) != 0;
364     }
365     @property bool isV4Mapped() pure const @trusted @nogc {
366         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
367         return IN6_IS_ADDR_V4MAPPED(cast(in6_addr*)&inaddr) != 0;
368     }
369     /// Returns true if this address is in the IPv4 range
370     @property bool isV4Compat() pure const @trusted @nogc {
371         // DMDBUG the "macro" is incorrectly defined, which means we have to cast away constness here
372         return IN6_IS_ADDR_V4COMPAT(cast(in6_addr*)&inaddr) != 0;
373     }
374 
375     static assert (this.sizeof == in6_addr.sizeof, "Incorrect size of struct");
376 }
377 
378 unittest {
379     assert(IPv6.loopback.toString() == "::1");
380     assert(IPv6.loopback.isLoopback);
381 }
382 
383 /// A D representation of the `sockaddr_in6` struct
384 struct SockAddrIPv6 {
385     /// Unspecified port constant
386     enum PORT_ANY = 0;
387 
388     /// the underlying `sockaddr_in6` struct
389     sockaddr_in6 sa;
390 
391     /// Construct a SockAddrIPv6
392     this(in6_addr addr, ushort port = PORT_ANY) nothrow @safe @nogc {
393         sa.sin6_family = AF_INET6;
394         sa.sin6_addr = addr;
395         sa.sin6_flowinfo = 0;
396         this.port = port;
397     }
398 
399     /// ditto
400     this(string addr, ushort port = PORT_ANY) @trusted @nogc {
401         int res = inet_pton(AF_INET6, toStringzNGC(addr), &sa.sin6_addr);
402 
403         DBG_ASSERT!"Invalid call to inet_pton"(res>=0);
404         if( res!=1 ) {
405             errno = EINVAL;
406             throw mkEx!ErrnoException("Invalid IPv6 address to SockAddrIPv6 constructor");
407         }
408 
409         sa.sin6_family = AF_INET6;
410         this.port = port;
411     }
412 
413     /// ditto
414     this(const sockaddr* sa, socklen_t length) nothrow @trusted @nogc {
415         ASSERT!"Wrong address family for IPv6. %s instead of %s"(sa.sa_family == AF_INET6, sa.sa_family, AF_INET6);
416         ASSERT!"IPv4 sockaddr too short. %s<%s"(length < sockaddr_in6.sizeof, length, sockaddr_in6.sizeof);
417         this.sa = *cast(sockaddr_in6*)sa;
418     }
419 
420     /// ditto
421     this(IPv6 addr, ushort port = PORT_ANY) nothrow @safe @nogc {
422         this(addr.inaddr, port);
423     }
424 
425     /// Get/set the sa's port in $(B host) byte order
426     @property void port(ushort newPort) nothrow @safe @nogc {
427         sa.sin6_port = htons(newPort);
428     }
429 
430     /// ditto
431     @property ushort port() const pure nothrow @safe @nogc {
432         return ntohs(sa.sin6_port);
433     }
434 
435     /// Convert the address to GC allocated string in the format addr:port
436     string toString() const nothrow @safe {
437         return toStringAddr() ~ ":" ~ toStringPort();
438     }
439 
440     /// Convert just the address part to a GC allocated string
441     string toStringAddr() const nothrow @trusted {
442         if( sa.sin6_family != AF_INET6 )
443             return "<Invalid IPv6 address>";
444 
445         char[INET6_ADDRSTRLEN] buffer;
446         ASSERT!"Address translation failed"(inet_ntop(AF_INET6, &sa.sin6_addr, buffer.ptr, INET6_ADDRSTRLEN) !is null);
447 
448         return to!string(buffer.ptr);
449     }
450 
451     auto toFixedStringAddr() const nothrow @trusted @nogc {
452         ASSERT!"Address family is %s, not IPv6"( sa.sin6_family == AF_INET6, sa.sin6_family );
453 
454         FixedString!INET6_ADDRSTRLEN buf;
455         buf.length = buf.capacity;
456         ASSERT!"Address translation failed"(inet_ntop(AF_INET6, &sa.sin6_addr, buf.ptr, buf.len) !is null);
457         setStringzLength(buf);
458 
459         return buf;
460     }
461 
462     /// Convert just the port part to a GC allocated string
463     string toStringPort() const nothrow @safe {
464         if( port!=PORT_ANY )
465             return to!string(port);
466         else
467             return "*";
468     }
469 
470     /// Construct a loopback sockaddr for the given port
471     static SockAddrIPv6 loopback(ushort port = PORT_ANY) nothrow @safe @nogc {
472         return SockAddrIPv6(IPv6.loopback, port);
473     }
474 
475     /// Construct an any sockaddr for the given port
476     static SockAddrIPv6 any(ushort port = PORT_ANY) nothrow @safe @nogc {
477         return SockAddrIPv6(IPv6.any, port);
478     }
479 }
480 
481 unittest {
482     // String translation tests
483     in6_addr test_ip6 = in6_addr(['\x11','\x11','\x11','\x11',
484                              '\x11','\x11','\x11','\x11',
485                              '\x11','\x11','\x11','\x11',
486                              '\x11','\x11','\x11','\x11']);
487 
488     SockAddrIPv6 s1 = SockAddrIPv6.loopback(1234);
489     SockAddrIPv6 s2 = SockAddrIPv6(test_ip6, 1234);
490 
491     assertEQ(s1.toString(), "::1:1234");
492     assertEQ(s1.toFixedStringAddr(), "::1");
493     assertEQ(s2.toString(), "1111:1111:1111:1111:1111:1111:1111:1111:1234");
494     assertEQ(s2.toFixedStringAddr(), "1111:1111:1111:1111:1111:1111:1111:1111");
495 }
496 
497 /// A D representation of the `sockaddr_un` struct
498 struct SockAddrUnix {
499     /// the underlying `sockaddr_in` struct
500     sockaddr_un unix = void;
501 
502     /// Construct a SockAddrUnix
503     this(const sockaddr* sa, socklen_t length) nothrow @trusted @nogc {
504         ASSERT!"Wrong address family for Unix domain sockets. %s instead of %s"(sa.sa_family == AF_UNIX, sa.sa_family, AF_UNIX);
505         ASSERT!"Unix domain sockaddr too short. %s<%s"(length < sockaddr_un.sizeof, length, sockaddr_un.sizeof);
506         this.unix = *cast(sockaddr_un*)sa;
507     }
508 
509     /// ditto
510     this(string path) nothrow @trusted @nogc {
511         unix.sun_family = AF_UNIX;
512         unix.sun_path[0..path.length][] = cast(immutable(byte)[])path[];
513         if( path.length < unix.sun_path.length )
514             unix.sun_path[path.length] = '\0';
515     }
516 
517     /// Convert the address to GC allocated string
518     string toString() @trusted {
519         import std..string;
520 
521         if( unix.sun_family != AF_UNIX )
522             return "<Invalid Unix domain address>";
523 
524         if( unix.sun_path[0] == '\0' )
525             return "<Anonymous Unix domain address>";
526 
527         auto idx = indexOf(cast(char[])(unix.sun_path[]), cast(byte)'\0');
528         if( idx<0 )
529             idx = unix.sun_path.length;
530 
531         return to!string(unix.sun_path[0..idx]);
532     }
533 }
534 
535 /// A D representation of a `sockaddr` struct
536 ///
537 /// This is how `sockaddr` might have looked like had C supported inheritence
538 struct SockAddr {
539     align(1):
540     union {
541         sockaddr base = sockaddr(AF_UNSPEC);    /// SockAddr as a `sockaddr`
542         SockAddrIPv4 ipv4;                      /// SockAddr as a SockAddrIPv4
543         SockAddrIPv6 ipv6;                      /// SockAddr as a SockAddrIPv6
544         SockAddrUnix unix;                      /// SockAddr as a SockAddrUnix
545     }
546 
547     /// Construct a SockAddr
548     this(const sockaddr* sa, socklen_t length) nothrow @safe @nogc {
549         switch(sa.sa_family) {
550         case AF_INET:
551             this.ipv4 = SockAddrIPv4(sa, length);
552             break;
553         case AF_INET6:
554             this.ipv6 = SockAddrIPv6(sa, length);
555             break;
556         case AF_UNIX:
557             this.unix = SockAddrUnix(sa, length);
558             break;
559         default:
560             ASSERT!"SockAddr constructor called with invalid family %s"(false, sa.sa_family);
561         }
562     }
563 
564     /// ditto
565     this(SockAddrIPv4 sa) nothrow @safe @nogc {
566         ipv4 = sa;
567         ASSERT!"Called with mismatching address family. Expected IPv4(%s), got %s"( AF_INET == family, AF_INET, family );
568     }
569 
570     /// ditto
571     this(SockAddrIPv6 sa) nothrow @safe @nogc {
572         ipv6 = sa;
573         ASSERT!"Called with mismatching address family. Expected IPv6(%s), got %s"( AF_INET6 == family, AF_INET6, family );
574     }
575 
576     /// ditto
577     this(SockAddrUnix sa) nothrow @safe @nogc {
578         unix = sa;
579         ASSERT!"Called with mismatching address family. Expected Unix domain(%s), got %s"( AF_UNIX == family, AF_UNIX, family );
580     }
581 
582     /// Return the address family
583     @property sa_family_t family() const pure nothrow @safe @nogc {
584         return base.sa_family;
585     }
586 
587     /// Return a GC allocated string representing the address
588     string toString() @safe {
589         switch( family ) {
590         case AF_UNSPEC:
591             return "<Uninitialized socket address>";
592         case AF_INET:
593             return ipv4.toString();
594         case AF_INET6:
595             return ipv6.toString();
596         case AF_UNIX:
597             return unix.toString();
598 
599         default:
600             return "<Unsupported socket address family>";
601         }
602     }
603 
604     /// Convert just the address part to a GC allocated string
605     string toStringAddr() @safe {
606         switch( family ) {
607         case AF_UNSPEC:
608             return "<Uninitialized socket address>";
609         case AF_INET:
610             return ipv4.toStringAddr();
611         case AF_INET6:
612             return ipv6.toStringAddr();
613         case AF_UNIX:
614             return unix.toString();
615 
616         default:
617             return "<Unsupported socket address family>";
618         }
619     }
620 
621     /// Convert just the port part to a GC allocated string
622     string toStringPort() @safe {
623         switch( family ) {
624         case AF_UNSPEC:
625             return "<Uninitialized socket address>";
626         case AF_INET:
627             return ipv4.toStringPort();
628         case AF_INET6:
629             return ipv6.toStringPort();
630         case AF_UNIX:
631             return "<Portless address family AF_UNIX>";
632 
633         default:
634             return "<Unsupported socket address family>";
635         }
636     }
637 
638     /// Perform a name resolution on the given string
639     static SockAddr resolve(string hostname, string service = null, ushort family = AF_INET, int sockType = 0) @trusted
640     {
641         ASSERT!"Invalid family %s"(family == AF_INET || family == AF_INET6, family);
642 
643         addrinfo* res = null;
644         addrinfo hint;
645         hint.ai_family = family;
646         hint.ai_socktype = sockType;
647 
648         auto rc = getaddrinfo(hostname.toStringzNGC, ToStringz!512(service), &hint, &res);
649         if( rc!=0 ) {
650             throw mkExFmt!Exception("Lookup failed for %s:%s: %s", hostname, service, to!string(gai_strerror(rc)));
651         }
652         if( res is null ) {
653             throw mkExFmt!Exception("Lookup for %s:%s returned no results", hostname, service);
654         }
655         scope(exit) freeaddrinfo(res);
656 
657         return SockAddr(res.ai_addr, res.ai_addrlen);
658     }
659 
660     unittest {
661         auto addr = SockAddr.resolve("localhost", "ssh");
662         assertEQ( addr.toString(), "127.0.0.1:22" );
663         addr = SockAddr.resolve("localhost");
664         assertEQ( addr.toString(), "127.0.0.1:*" );
665     }
666 
667     /// Returns the length of the data in the struct
668     ///
669     /// This is needed for underlying system calls that accept `sockaddr`
670     @property uint len() const pure nothrow @safe @nogc {
671         switch(family) {
672         case AF_UNSPEC:
673             return SockAddr.sizeof;
674         case AF_INET:
675             return SockAddrIPv4.sizeof;
676         case AF_INET6:
677             return SockAddrIPv6.sizeof;
678         case AF_UNIX:
679             return SockAddrUnix.sizeof;
680         default:
681             assert(false, "Unknown family");
682         }
683     }
684 }