1 /++ 2 Parsing of all kind of socket addresses 3 4 ## Currently supported 5 6 $(LIST 7 * Unix Domain Socket path (e.g. `/var/run/myapp.sock`) 8 * IPv4 address (e.g. `192.168.0.1`) 9 * IPv4 address with port (e.g. `192.168.0.1:1234`) 10 * IPv6 address (e.g. `[2001:0db8::1]`) 11 * IPv6 address with port (e.g. `[2001:0db8::1]:1234`) 12 ) 13 14 ## Rules 15 16 $(LIST 17 * Unix Domain socket paths MUST be absolute (i.e. start with `/`) to prevent ambiguity. 18 * IPv4 resp. IPv6 addresses are separated from their (optional) accompanying port number by `:`. 19 * IPv6 addresses MUST be encapsulated in square brackets (`[…]`). (This prevents ambiguity with regard to the address/port separator `:`.) 20 ) 21 22 ## Idea 23 24 Socket addresses are intended to provide a concise way to describe the “address” of network socket. 25 26 The original use case was to provide a simple way to describe listening sockets in a uniform way. 27 For a command line interface there should only be a single `--socket=<address>` parameter, 28 regardless of whether the specified address is IPv4 or IPv6 – or even a Unix Domain Socket path. 29 30 ### Relation to URIs 31 32 While similarly looking, socket addresses neither are URIs nor to be considered a part/subset of them. 33 34 For example, in comparison to URIs there’s no protocol scheme or path. 35 Also there are no domain names in socket addresses (thus no DNS resolution necessary or provided). 36 +/ 37 module socketplate.address; 38 39 import std.ascii : isDigit; 40 import std.conv : to; 41 import std.string : indexOf; 42 43 @safe pure nothrow: 44 45 struct SocketAddress 46 { 47 /// 48 Type type = Type.invalid; 49 50 /// 51 string address = null; 52 53 /// 54 int port = int.min; 55 56 /// 57 enum Type 58 { 59 invalid = -1, 60 61 /// Unix Domain Socket 62 unixDomain = 0, 63 64 /// Internet Protocal Version 4 (IPv4) address 65 ipv4, 66 67 /// Internet Protocal Version 6 (IPv6) address 68 ipv6, 69 } 70 } 71 72 /++ 73 Socket address triage and parsing function 74 75 Determines the type (IPv4, IPv6, Unix Domain Socket etc.) of a socket 76 and parses it according to the rules mentioned in this module’s description. 77 78 $(WARNING 79 Does not actually validate addresses (or paths). 80 ) 81 82 Returns: 83 true = on success, or 84 false = on error (invalid input) 85 +/ 86 bool parseSocketAddress(string input, out SocketAddress result) 87 { 88 // Unix Domain Socket 89 if (input[0] == '/') 90 return parseUnixDomain(input, result); 91 92 // IPv6 93 if (input[0] == '[') 94 return parseIPv6(input, result); 95 96 // IPv4 97 98 // basic garbage detection 99 foreach (ref c; input) 100 if ((!c.isDigit) && (c != '.') && (c != ':')) 101 return false; 102 103 return parseIPv4(input, result); 104 } 105 106 /// 107 unittest 108 { 109 SocketAddress sockAddr; 110 111 assert(parseSocketAddress("127.0.0.1:8080", sockAddr)); 112 assert(sockAddr == SocketAddress(SocketAddress.Type.ipv4, "127.0.0.1", 8080)); 113 114 assert(parseSocketAddress("127.0.0.1", sockAddr)); 115 assert(sockAddr == SocketAddress(SocketAddress.Type.ipv4, "127.0.0.1", int.min)); 116 117 assert(parseSocketAddress("[::]:993", sockAddr)); 118 assert(sockAddr == SocketAddress(SocketAddress.Type.ipv6, "::", 993)); 119 120 assert(parseSocketAddress("[::1]", sockAddr)); 121 assert(sockAddr == SocketAddress(SocketAddress.Type.ipv6, "::1", int.min)); 122 123 assert(parseSocketAddress("/var/run/myapp.sock", sockAddr)); 124 assert(sockAddr == SocketAddress(SocketAddress.Type.unixDomain, "/var/run/myapp.sock", int.min)); 125 126 assert(!parseSocketAddress("myapp.sock", sockAddr)); 127 assert(!parseSocketAddress("::1", sockAddr)); 128 assert(!parseSocketAddress("http://127.0.0.1", sockAddr)); 129 } 130 131 /// 132 SocketAddress makeSocketAddress(string address, ushort port) 133 { 134 assert(address.length >= 4, "Invalid IP address"); 135 136 // IPv6? 137 if (address[0] == '[') 138 return SocketAddress( 139 SocketAddress.Type.ipv6, 140 address[1 .. ($ - 1)], 141 port 142 ); 143 144 // IPv4? 145 else if (address[0].isDigit) 146 return SocketAddress( 147 SocketAddress.Type.ipv4, 148 address, 149 port 150 ); 151 152 assert(false, "Invalid IP address"); 153 } 154 155 /// 156 SocketAddress makeSocketAddress(string unixDomainSocketPath) 157 { 158 return SocketAddress(SocketAddress.Type.unixDomain, unixDomainSocketPath); 159 } 160 161 private: 162 163 bool parseUnixDomain(string input, out SocketAddress result) @nogc 164 { 165 result = SocketAddress(SocketAddress.Type.unixDomain, input); 166 return true; 167 } 168 169 bool parseIPv6(string input, out SocketAddress result) 170 { 171 immutable ptrdiff_t idxEndOfAddress = input.indexOf(']'); 172 if (idxEndOfAddress < 0) 173 return false; 174 175 int port = int.min; 176 177 if ((idxEndOfAddress + 1) < input.length) 178 { 179 string portStr = input[(idxEndOfAddress + 1) .. $]; 180 if (portStr[0] != ':') 181 return false; 182 183 portStr = portStr[1 .. $]; 184 immutable portValid = parsePort(portStr, port); 185 if (!portValid) 186 return false; 187 } 188 189 immutable string address = input[1 .. idxEndOfAddress]; 190 191 result = SocketAddress(SocketAddress.Type.ipv6, address, port); 192 return true; 193 } 194 195 bool parseIPv4(string input, out SocketAddress result) 196 { 197 ptrdiff_t idxPortSep = input.indexOf(':'); 198 199 if (idxPortSep == 0) 200 return false; 201 202 int port = int.min; 203 204 if (idxPortSep > 0) 205 { 206 immutable portValid = parsePort(input[(idxPortSep + 1) .. $], port); 207 if (!portValid) 208 return false; 209 } 210 211 immutable string address = (idxPortSep > 0) ? input[0 .. idxPortSep] : input; 212 213 result = SocketAddress(SocketAddress.Type.ipv4, address, port); 214 return true; 215 } 216 217 bool parsePort(scope string input, out int result) 218 { 219 try 220 result = input.to!ushort; 221 catch (Exception) 222 return false; 223 224 return true; 225 }